排序详解
一、选择排序
代码:
void SelectSort(int* arr,int len)
{
int begin = 0;
int end = len - 1;
while(begin < end)
{
int min_index = begin;
int max_index = end;
for(int i = begin;i <= end;i++)
{
if(arr[i] > arr[max_index])
{
max_index = i;
}
if(arr[i] < arr[min_index])
{
min_index = i;
}
}
Swap(&arr[begin],&arr[min_index]);
//交换的时候需要注意,如果最大值刚好在begin位置处,经过前面的交换,最大值已经杯换到最小下标处
if(max_index == begin)
{
max_index = min_index;
}
Swap(&arr[end],&arr[max_index]);
begin++;
end--;
}
}
思想:
- 上述代码的选择排序是先定义两个下标min_index,和max_index,用来记录每趟比较中最小元素和最大元素的下标
- 然后在循环内部进行选择记录,每趟循环结束min_index和max_index已经记录该趟的最大元素和最小元素的下标,
- 然后进行交换,把最小的元素交换到数组的begin位置处,最大的元素交换到end位置处,
- 注意⚠️在交换完最小元素后需要进行判断,因为如果刚好最大元素就是在begin位置处,那么后序要交换最大元素到end位置处就会出错,因为如果最大元素在begin的情况下,经过前面的交换,现在最大元素的位置应该在min_index下标处,所以应更新最大元素位置的下标
- 在元素集合array[i]–array[n-1]中选择关键码最大(小)的数据元素
- 若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换
- 在剩余的array[i]–array[n-2](array[i+1]–array[n-1])集合中,重复上述步骤,直到集合剩余1个元素
二、插入排序
代码:
void InsertSort(int* arr,int len)
{
int end = 0;
for(end =0 ;end < len -1;end ++)
{
int temp = arr[end +1];
while(end >= 0)
{
if(arr[end] > temp)
{
arr[end + 1] = arr[end];
end--;
}
else
{
break;
}
}
arr[end + 1] = temp;
}
}
思想:
- 先定义end来标识有序序列的最后一个元素的下标,
- 再把end后的第一个元素temp往有序序列内插
- 如果temp比end位置元素大,就直接往end+1位置放temp即可
- 如果temp比end位置元素小,就把end位置元素放到end+1位置处,继续循环向前判断
- 元素集合越接近有序,直接插入排序算法的时间效率越高
- 时间复杂度:O(N^2)
- 空间复杂度:O(1),它是一种稳定的排序算法
- 稳定性:稳定
- 当插入第i(i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,此时用array[i]的排序码与
array[i-1],array[i-2],…的排序码顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移
三、希尔排序
代码:
void ShellSort(int* arr,int len)
{
int gap = len;
while(gap > 1)
{
gap = gap / 3 +1;
for(int i = 0;i < len - gap;i++)
{
int end = i;
int temp = arr[end + gap];
while(end >= 0)
{
if(arr[end] > temp)
{
arr[end + gap] = arr[end];
end -= gap;
}
else
{
break;
}
}
arr[end + gap] = temp;
}
}
}
思想:
- 定义一个gap为分得的组数,gap越大越不接近有序,所以gap应是动态变化的,直到为1,就是插入排序
- 希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个
组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工
作。当到达=1时,所有记录在统一组内排好序 - 希尔排序的时间复杂度不好计算,需要进行推导,推导出来平均时间复杂度: O(N1.3—N2)
- 稳定性:不稳定
四、堆排
代码:
//向下调整
void adjust(int* arr,int len,int index)
{
int parent = index;
int child = parent * 2 + 1;
while(child < len)
{
if(child + 1 < len && arr[child] < arr[child + 1])
{
child = child + 1;
}
if(arr[child] > arr[parent])
{
Swap(&arr[child],&arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
//堆排
void HeapSort(int* arr,int len)
{
for(int i = (len - 1 - 1 ) / 2;i >= 0;i--)
{
adjust(arr, len, i);
}
int end = len - 1;
while(end > 0)
{
Swap(&arr[0],&arr[end]);
adjust(arr,end,0);
end--;
}
}
思想:
- 堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆
- 注意向下调整算法的写法,首先从倒数第一个非叶子节点开始调整,把整棵树调整为大根堆
- 建大堆使,取左右两个孩子中较大的和父亲节点比较,看是否需要交换,直到把整棵树调整为大根堆
- 排序的过程就是把堆顶元素和数组的最后一个元素进行交换,再次进行向下调整,注意此时,交换过来的堆顶元素就相当于是有序的,再次进行向下调整时就把数组的元素个数-1,一次类推,循环排序
- 堆排序使用堆来选数,效率就高了很多。
- 时间复杂度:O(N*logN)
- 空间复杂度:O(1)
- 稳定性:不稳定
五、冒泡排序:
这个比较简单,直接来代码:
void bubbleSort(int *str, int len)
{
//冒泡排序, 升序排列
//数组当中比较小的数值向下沉,数值比较大的向上浮!
int i = 0;//趟数
int flg ;
for(i = 0;i < len-1;i++)
{
int j = 0;//次数
flg = 0;//如果第一次已经是排序好了,就不用排序了,直接brek
for(j = 0;j < len-1-i;j++)
{
if(str[j] > str[j+1])
{
int tmp = str[j];
str[j] = str[j+1];
str[j+1] = tmp;
flg = 1;
}
}
if(!flg)
{
break;
}
}
}
六、快速排序hoare版本
int QuickSortChild1(int* arr,int begin,int end)
{
int key = begin;
while(begin < end)
{
while(begin < end && arr[end] >= arr[key])
{
end--;
}
while(begin < end && arr[begin] <= arr[key])
{
begin++;
}
Swap(&arr[end],&arr[begin]);
}
Swap(&arr[key],&arr[begin]);
return begin;
}
//快排
void QuickSort(int* arr,int begin,int end)
{
//注意递归出口
if(begin < end)
{
int mid = QuickSortChild1(arr,begin,end);
QuickSort(arr,begin,mid - 1);
QuickSort(arr,mid + 1,end);
}
}
七、快速排序 挖坑法
int QuickSortChild2(int* arr,int begin,int end)
{
int key =arr[begin];
while(begin < end)
{
while(begin < end && arr[end] >= key)
{
end--;
}
arr[begin] = arr[end];
while(begin < end && arr[begin] <= key)
{
begin++;
}
arr[end] = arr[begin];
}
arr[begin] = key;
return begin;
}
void QuickSort(int* arr,int begin,int end)
{
//注意递归出口
if(begin < end)
{
int mid = QuickSortChild2(arr,begin,end);
QuickSort(arr,begin,mid - 1);
QuickSort(arr,mid + 1,end);
}
}
八、快速排序前后指针版本
//双指针法
int QuickSortChild3(int* arr,int begin,int end)
{
int pref = begin - 1;
int cur = begin;
int key = end;
//定义pref,cur 。pref指向cur的前一个
while(cur < end)
{
//cur去找比key小的元素,如果找到,就把pref++,就交换cur和pref下标所在的元素
if(arr[cur] < arr[key])
{
pref++;
Swap(&arr[cur],&arr[pref]);
}
//如果cur所在下标的元素比key所在元素大,就直接后移cur
cur++;
}
//最后注意和end位置的元素交换的是pref的下一个,不是它自己
Swap(&arr[++pref],&arr[end]);
return pref;
}
void QuickSort(int* arr,int begin,int end)
{
//注意递归出口
if(begin < end)
{
int mid = QuickSortChild3(arr,begin,end);
QuickSort(arr,begin,mid - 1);
QuickSort(arr,mid + 1,end);
}
}
注意:双指针法是用cur去找比key小的元素,如果找到就把pref++,交换Swap(&arr[cur],&arr[pref]);继续循环,最后注意不是拿pref和key进行交换,而是拿pref的下一个和key进行交换Swap(&arr[++pref],&arr[end]);
九、快速排序优化–三数取中
//快排优化--->三数取中,防止有序的情况
void medianOfThree(int* arr,int begin,int end)
{
int mid = (begin + end) >> 1;
if(arr[mid] > arr[begin])
{
Swap(&arr[mid],&arr[begin]);
}
if(arr[mid] > arr[end])
{
Swap(&arr[mid],&arr[end]);
}
if(arr[end] < arr[begin])
{
Swap(&arr[end],&arr[begin]);
}
}
void QuickSort(int* arr,int begin,int end)
{
//注意递归出口
if(begin < end)
{
medianOfThree(arr,begin,end);
int mid = QuickSortChild3(arr,begin,end);
QuickSort(arr,begin,mid - 1);
QuickSort(arr,mid + 1,end);
}
}
十、快速排序----小区间优化
//快排优化--->小区间优化 + 三数取中
void QuickSort2(int* arr,int begin,int end)
{
if(begin < end)
{
//小区间就直接使用选择排序,注意边界条件的控制
if(end - begin + 1 < 10)
{
SelectSort(arr + begin,end - begin + 1);
}
else
{
medianOfThree(arr,begin,end);
int mid = QuickSortChild3(arr,begin,end);
QuickSort1(arr,begin,mid - 1);
QuickSort1(arr,mid + 1,end);
}
}
}
十一、快速排序非递归版:
//快速排序非递归(用数组代替栈)
void quickSort3(int *str, int len)
{
int stack[MaxSize * 2];
int top = 0;
int low = 0;
int hight = len - 1;
int mid = QuickShortchild(str, low, hight);//先进行一趟快排,得到一个分界点,
//判断mid左右两边是否两个数据以上,如果有,分别把左右两边元素下标入栈
if (mid > low + 1)
{
stack[top++] = low;
stack[top++] = mid - 1;
}
if (mid < hight - 1)
{
stack[top++] = mid + 1;
stack[top++] = hight;
}
//以上代码执行完毕,说明下标入栈完毕
//判断栈是否为空,如果不为空,取出两个下标赋给hight,low,后进行循环,直至栈为空。
while (top > 0)
{
hight = stack[--top];
low = stack[--top];
mid = QuickShortchild(str, low, hight);
if (mid > low + 1)
{
stack[top++] = low;
stack[top++] = mid - 1;
}
if (mid < hight - 1)
{
stack[top++] = mid + 1;
stack[top++] = hight;
}
}
}
十二、归并排序:
//归并排序
void mergeChild(int *str,int start,int mid,int end)//把一个一个的序列排序并合并在一起
{
int tempstr[MaxSize];
int tempIndex = start;
int start2 = mid + 1;
int i = start;
while (start <= mid && start2 <= end)//两个归并段都有数据的时候
{
if (str[start] <= str[start2])
{
tempstr[tempIndex] = str[start];
start++;
tempIndex++;
}
else
{
tempstr[tempIndex] = str[start2];
start2++;
tempIndex++;
}
}
while (start <= mid)
{
tempstr[tempIndex] = str[start];
start++;
tempIndex++;
}
while (start2 <= end)
{
tempstr[tempIndex] = str[start2];
start2++;
tempIndex++;
}
while (i <= end)//把每次合并排序完成的元素拷贝到原数组中
{
str[i] = tempstr[i];
i++;
}
}
void mergeSort(int *str, int start,int end)
{
if (start >= end)
{
return;
}
int mid = (start + end) / 2;//该过程是把数组分解为一个一个的序列
mergeSort(str,start, mid);
mergeSort(str, mid+1 , end);
mergeChild(str,start,mid ,end);//合并和排序过程
}
基本思想:
-
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and
Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有
序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤 -
归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
-
时间复杂度:O(N*logN)
-
空间复杂度:O(N)
-
稳定性:稳定