文章目录

  • 1. 内部排序的基本种类
  • 2. 插入排序
    • Ⅰ直接插入排序
    • 性能与稳定性分析
    • Ⅱ 折半插入排序
    • 性能与稳定性分析
    • Ⅲ 希尔排序
    • 性能与稳定性分析
  • 3. 交换排序
    • Ⅰ 冒泡排序
    • 性能与稳定性分析
    • Ⅱ 快速排序
      • key值选取(三数取中)
      • 三种交换方法
        • ① hoare左右指针
        • ② 挖坑法
        • ③ 前后指针法
      • 快速排序递归写法
        • 小区间优化
      • 快速排序非递归写法
    • 性能与稳定性分析
  • 4. 选择排序
    • Ⅰ 简单选择排序
    • 性能与稳定性分析
    • Ⅱ 堆排序
    • 性能与稳定性分析
  • 5. 归并排序(二路)
    • Ⅰ 归并排序递归写法
    • Ⅱ 归并排序非递归写法
    • 性能与稳定性分析
  • 6. 计数排序(哈希表排序)
    • 性能与稳定性分析
  • 7. 基数排序(桶排序)
    • 性能与稳定性分析
  • 8. 内部排序总结
  • 9. 内部排序与外部排序
  • 附录
    • C++实现上述排序所有细节
    • Java实现上述排序所有细节
  • 代码实现位置

这篇博客是在我之前写的排序博客补充而来,未来还会补充Python实现。 这里的排序都以从小到大排序为例。

结合王道计算机考研数据结构第八章浏览更好一些。 这里将书上的代码更通俗的形式列出,并添加一些拓展与思考。不仅仅适合考研,面试复习也应该够了

这篇博客会使用C/C++/Java实现,但因为C语言没有类似C++STL库,自己实现太麻烦了,所以像是快速排序的非递归实现只会通过C++和Java实现。

为了让整个排序的文章不至于过于零碎,这里把C++/Java/Python实现放到附录中,思路大致相同,代码也大致相同。主要是重复复习

1. 内部排序的基本种类

插入排序

  • 直接插入排序
  • 折半插入排序
  • 希尔排序

交换排序

  • 冒泡排序
  • 快速排序

选择排序

  • 简单选择排序
  • 堆排序

归并排序(二路)

  • 归并排序

计数排序

  • 计数排序

2. 插入排序

插入排序基本思想:每次将一个待排序的记录按照其关键字大小插入前面已经拍好序的子序列中,直到全部元素插入完成

Ⅰ直接插入排序

#pragma once
#include<stdio.h>//打印数组测试
void PrintArr(int* arr, int size) {for (int i = 0; i < size; i++) {printf("%d ",arr[i]);}printf("\n");
}//直接插入排序
//传入参数是数组的首元素指针以及数组的大小
void InsertSort(int* arr, int size) {for (int i = 0; i < size - 1; i++) {int endpos = i;//有序子序列的末尾下标int next = arr[endpos + 1];while (endpos >= 0) {if (arr[endpos] > next) {//从小到大排序arr[endpos + 1] = arr[endpos];//这个endpos位置的元素向后挪endpos--;}else {//找到next该放的位置了,next放到endpos+1位置break;}}arr[endpos + 1] = next;
}

性能与稳定性分析

直接插入排序:

  • 空间复杂度:使用了常数个辅助单元。空间复杂度为O(1)
  • 时间复杂度:遍历一遍数组,每次遍历时需要对比有序子序列长度次(近似为N次),所以为O(N^2)

稳定性:元素比较是从后向前进行比较,因为arr[endpos] > next时endpos才会发生移动,所以相同值的相对位置不会发生改变,是稳定排序。

Ⅱ 折半插入排序

折半插入排序与直接插入类似,只是在每次从有序的子序列找next应该插入的位置时采用折半查找法。(因为把next前子序列之前就是有序的)

//折半插入排序
//传入参数是数组的首元素指针以及数组的大小
int BinarySearch(int* arr, int left, int right, int target) {while (left <= right) {int mid = (left + right) >> 1;if (arr[mid] > target) {right = mid - 1;}else if (arr[mid] < target) {left = mid + 1;}else {//相等,保证稳定排序插入到这个元素的下一个位置left = mid + 1;break;}}return left;
}
void InsertSortUseBisearch(int* arr, int size) {for (int i = 0; i < size - 1; i++) {int endpos = i;int next = arr[endpos + 1];//设置折半查找范围[0,endpos]int pos = BinarySearch(arr, 0, endpos, next);//将pos到endpos的元素进行挪动for (int j = endpos; j >= pos; j--) {arr[j + 1] = arr[j];}arr[pos] = next;}
}

性能与稳定性分析

折半插入排序:

  • 空间复杂度:使用了常数个辅助单元。空间复杂度为O(1)
  • 时间复杂度:折半查找法减少了比较次数,但元素的移动次数没有改变,所以时间复杂度为O(N^2);

稳定性:元素比较是从后向前进行比较,因为折半搜索时,遇到相同元素,下标取的是相同元素下标的下一个,所以相同值的相对位置不会发生改变,是稳定排序。

Ⅲ 希尔排序

直接插入法如果顺序表有序时,时间复杂度为O(N),只需要遍历一般数组即可

所以:希尔排序与直接插入排序的思想类似,希尔排序思想是先大区间进行排序,让大的数字尽量往数组后面走,小的数字尽量往前面走。而在这个过程中处理的方式与直接插入排序相同。

先让数组尽可能快速的向有序靠近,之后再执行直接插入排序。尽可能提升效率。

//希尔排序
//传入参数是数组的首元素指针以及数组的大小
void ShellSort(int* arr, int size) {int step = size;//希尔每次处理数组的步长,最后步长为1执行直接插入排序while (step > 1) {step = step / 3 + 1;//为了让step最小值为1,一定能执行一次直接插入排序for (int i = 0; i < size - step; i++) {int endpos = i;int next = arr[endpos + step];while (endpos>=0){if (next < arr[endpos]) {arr[endpos + step] = arr[endpos];endpos -= step;}else {break;}}arr[endpos + step] = next;}}
}

性能与稳定性分析

希尔排序:

  • 空间复杂度:使用了常数个辅助单元。空间复杂度为O(1)
  • 时间复杂度:希尔排序在最坏的情况下时间复杂度为O(N^2)

稳定性:因为是将数组分段快速进行有序靠近,所以相同大小的元素可能分到不同的段上,所以希尔排序不是稳定的排序。

3. 交换排序

Ⅰ 冒泡排序

每次遍历将最大值移动到顺序表的尾部,一共遍历顺序表的长度次。

当某次的冒泡没有移动数据时,说明已经有序了,可以跳出循环排序结束。

//冒泡排序
//传入参数是数组的首元素指针以及数组的大小
void swap(int* left, int* right) {int tmp = *left;*left = *right;*right = tmp;
}
void BubbleSort(int* arr, int size) {int flag = 0;//标记某次冒泡是否交换顺序for (int i = 0; i < size; i++) {flag = 0;for (int j = 0; j < size - i - 1; j++) {if (arr[j] > arr[j + 1]) {flag = 1;swap(&arr[j], &arr[j + 1]);}}if (flag == 0) {//这次遍历没有交换过,数组已经有序了break;}}
}

性能与稳定性分析

冒泡排序:

  • 空间复杂度:使用了常数个辅助单元。空间复杂度为O(1)
  • 时间复杂度:遍历了N次,每次都近似需要遍历整个顺序表。时间复杂度O(N^2)

稳定性:上面的代码中arr[j] = arr[j + 1]不发生交换,所以冒泡排序是稳定的排序。

Ⅱ 快速排序

快速排序的思路:

  1. 先选出一个key单趟排序,排序结果为顺序表key前的数组都比key小,key后的数字都比key大。
  2. 递归或者使用栈处理key之前的数组和key之后的数组

注意:

  1. key的选择很影响快速排序的效率,如果这个数组本身就接近有序,key值选择不合适,快速排序就会退化为冒泡排序。O(N^2)
  2. 针对这种情况,设计了一种叫三数取中的优化

key值选取(三数取中)

  • 为了避免选择key使得每次分数组时key左数组/右数组没有元素,这里key选择为数组头部,数组尾部,数组中间的三个值中间大的那个
//传入数组,数组头,数组尾,三数取中,返回中间值的下标,数组区间[]形式
int GetMidIndex(int* arr, int left, int right) {int mid = (left + right) >> 1;if (arr[left] < arr[mid]) {if (arr[mid] < arr[right]) {//arr[left]<arr[mid]<arr[right]return mid;}else if (arr[right] > arr[left]) {//arr[right]<=arr[mid]//arr[left]<arr[right]<=arr[mid]return right;}else {//arr[right]<=arr[mid]//arr[right]<=arr[left]//arr[right]<=arr[left]<arr[mid]return left;}}else {//arr[left]>arr[mid]if (arr[mid] > arr[right]) {//arr[right]<arr[mid]<arr[left]return mid;}else if (arr[right] > arr[left]) {//arr[mid]<arr[left]<arr[right]return left;}else {//arr[mid]<arr[left]//arr[right]>=arr[mid]//arr[right]<=arr[left]//arr[mid]<arr[right]<arr[left]return right;}}
}

三种交换方法

选择好key后,要将数组分成两部分,前部分小于key,后部分大于key。

这里选择了三种方法进行处理

① hoare左右指针

  • 左比key大的,右边找比key小的,二者都找到后交换左右的值。
  • 开始时先把key放到数组区间的最左边。
  • 先从右边开始找比key小的,在从数组左边开始找比key大的。找到后进行交换
  • 最后交换数组最左边的key和数组left下标的值。
  • 因为先从右边找小与key的值,所以最后跳出循环,left位置一定比key小,直接交换即可。
  • 函数返回key值在数组的下标,方便调用方将数组分成两部分
//让数组分成三部分,<key key <key,函数返回key值在数组的下标,方便调用方将数组分成两部分
//传入数组的区间为[]
int HoareLeftRightPtr(int* arr, int left, int right) {int mid = GetMidIndex(arr, left, right);//key值的下标//把key放到最左边可以更方便的交换swap(&arr[left], &arr[mid]);int keypos = left;while (left < right) {while (arr[right] >= arr[keypos] && right > left) {//找小于key的位置right--;}while (arr[left] <= arr[keypos] && left < right) {//找大于key的位置left++;}swap(&arr[left], &arr[right]);}//这时left位置对应的值一定小于key值,就把key放到left这个位置上swap(&arr[left], &arr[keypos]);return left;
}

② 挖坑法

  • 数组最左边先为坑从右边开始找小,找到小的后放到左边的坑上
  • 此后右边交换的位置为坑从左边开始找大,找到后放到右边的坑上
  • 同理因为是先从右开始找,所以最后停下来时一定是左边找大找不到,或者right<left。此时left的位置一定时小于key值的。直接交换即可
//让数组分成三部分,<key key <key,函数返回key值在数组的下标,方便调用方将数组分成两部分
//传入数组的区间为[]
int PitExchangeMethod(int* arr, int left, int right) {int mid = GetMidIndex(arr, left, right);//key值的下标swap(&arr[left], &arr[mid]);//开始最左边是坑,此时这个位置保存的是key值,记录一下key值int key = arr[left];while (left < right) {while (arr[right] >= key && right > left) {right--;}arr[left] = arr[right];//此时right的这个位置成了新的坑while (arr[left] <= key && left < right) {//找大left++;}arr[right] = arr[left];}//left位置一定坑,left+1位置大于key,left-1位置小于keyarr[left] = key;return left;
}

③ 前后指针法

  • 定义两个下标prev,cur
  • 先把key值放到最左边
  • cur找小于key值的元素,cur初始化为prev的下一个位置。
  • cur找到小的位置,prev+1,交换prev和cur的值。(prev指向的是小于key值,所以要交换prev+1与cur的位置)
  • 直到cur走到数组尾
  • 最后交换最左边位置和prev的位置,prev上的值一定小于key值

注意:

  • 当next与prev相差距离为1时会出现自己和自己交换的情况,可以通过判断prev+1是否和next相等来避免。
//让数组分成三部分,<key key <key,函数返回key值在数组的下标,方便调用方将数组分成两部分
//传入数组的区间为[]
int PrevNextPtr(int* arr, int left, int right) {int mid = GetMidIndex(arr, left, right);//key的下标swap(&arr[left], arr[mid]);int keypos = left;int prev = left;int next = prev + 1;while (next <= right) {//因为数组区间为[],所以next=right时也要进行判断//next找比key值小的位置if (arr[keypos] > arr[next]) {prev++;if (prev != next) {swap(&arr[prev], &arr[next]);}}next++;}swap(&arr[prev], &arr[keypos]);return prev;
}

快速排序递归写法

递归的思想类似于二叉树的前序遍历。先把key排好,递归左区间,再递归右区间

//传入参数是数组的首元素指针以及数组的大小
void swap(int* left, int* right) {int tmp = *left;*left = *right;*right = tmp;
}
//快速排序
//传入数组,数组头,数组尾,三数取中,返回中间值的下标,数组区间[]形式
int GetMidIndex(int* arr, int left, int right) {int mid = (left + right) >> 1;if (arr[left] < arr[mid]) {if (arr[mid] < arr[right]) {//arr[left]<arr[mid]<arr[right]return mid;}else if (arr[right] > arr[left]) {//arr[right]<=arr[mid]//arr[left]<arr[right]<=arr[mid]return right;}else {//arr[right]<=arr[mid]//arr[right]<=arr[left]//arr[right]<=arr[left]<arr[mid]return left;}}else {//arr[left]>arr[mid]if (arr[mid] > arr[right]) {//arr[right]<arr[mid]<arr[left]return mid;}else if (arr[right] > arr[left]) {//arr[mid]<arr[left]<arr[right]return left;}else {//arr[mid]<arr[left]//arr[right]>=arr[mid]//arr[right]<=arr[left]//arr[mid]<arr[right]<arr[left]return right;}}
}
//让数组分成三部分,<key key >key,函数返回key值在数组的下标,方便调用方将数组分成两部分
//传入数组的区间为[]
int HoareLeftRightPtr(int* arr, int left, int right) {int mid = GetMidIndex(arr, left, right);//key值的下标//把key放到最左边可以更方便的交换swap(&arr[left], &arr[mid]);int keypos = left;while (left < right) {while (arr[right] >= arr[keypos] && right > left) {//找小于key的位置,注意是>=否则会死循环,一直会交换两个值等于keyposright--;}while (arr[left] <= arr[keypos] && left < right) {//找大于key的位置left++;}swap(&arr[left], &arr[right]);}//这时left位置对应的值一定小于key值,就把key放到left这个位置上swap(&arr[left], &arr[keypos]);return left;
}
//让数组分成三部分,<key key >key,函数返回key值在数组的下标,方便调用方将数组分成两部分
//传入数组的区间为[]
int PitExchangeMethod(int* arr, int left, int right) {int mid = GetMidIndex(arr, left, right);//key值的下标swap(&arr[left], &arr[mid]);//开始最左边是坑,此时这个位置保存的是key值,记录一下key值int key = arr[left];while (left < right) {while (arr[right] >= key && right > left) {right--;}arr[left] = arr[right];//此时right的这个位置成了新的坑while (arr[left] <= key && left < right) {//找大left++;}arr[right] = arr[left];}//left位置一定坑,left+1位置大于key,left-1位置小于keyarr[left] = key;return left;
}
//让数组分成三部分,<key key >key,函数返回key值在数组的下标,方便调用方将数组分成两部分
//传入数组的区间为[]
int PrevNextPtr(int* arr, int left, int right) {int mid = GetMidIndex(arr, left, right);//key的下标swap(&arr[left], &arr[mid]);int keypos = left;int prev = left;int next = prev + 1;while (next <= right) {//因为数组区间为[],所以next=right时也要进行判断//next找比key值小的位置if (arr[keypos] > arr[next]) {prev++;if (prev != next) {swap(&arr[prev], &arr[next]);}}next++;}swap(&arr[prev], &arr[keypos]);return prev;
}
//数组区间为[]
void _QuickSortRecursion(int* arr, int left, int right) {if (left >= right) {return;}int keypos = PrevNextPtr(arr, left, right);//int keypos = PitExchangeMethod(arr, left, right);//int keypos = HoareLeftRightPtr(arr, left, right);//三种移动方法任意选取_QuickSortRecursion(arr, left, keypos - 1);_QuickSortRecursion(arr, keypos + 1, right);
}
//传入参数是数组的首元素指针以及数组的大小
void QuickSort(int* arr, int size) {_QuickSortRecursion(arr,0,size-1);
}

小区间优化

  • 当数组递归处理到非常小的区间时,直接对小区间进行折半插入排序(或者其他排序方式),不需要继续递归了,这个具体在代码中体现
//折半插入排序
//传入参数是数组的首元素指针以及数组的大小
int BinarySearch(int* arr, int left, int right, int target) {while (left <= right) {int mid = (left + right) >> 1;if (arr[mid] > target) {right = mid - 1;}else if (arr[mid] < target) {left = mid + 1;}else {//相等,保证稳定排序插入到这个元素的下一个位置left = mid + 1;break;}}return left;
}
void InsertSortUseBisearch(int* arr, int size) {for (int i = 0; i < size - 1; i++) {int endpos = i;int next = arr[endpos + 1];//设置折半查找范围[0,endpos]int pos = BinarySearch(arr, 0, endpos, next);//将pos到endpos的元素进行挪动for (int j = endpos; j >= pos; j--) {arr[j + 1] = arr[j];}arr[pos] = next;}
}
//数组区间为[]
void _QuickSortRecursion(int* arr, int left, int right) {if (right - left > 5) {//数组的长度大于5的时候使用快速排序int keypos = PrevNextPtr(arr, left, right);//int keypos = PitExchangeMethod(arr, left, right);//int keypos = HoareLeftRightPtr(arr, left, right);//三种移动方法任意选取_QuickSortRecursion(arr, left, keypos - 1);_QuickSortRecursion(arr, keypos + 1, right);}else {//递归到长度小于5时使用折半插入排序InsertSortUseBisearch(arr + left, right - left + 1);}
}
//传入参数是数组的首元素指针以及数组的大小
void QuickSort(int* arr, int size) {_QuickSortRecursion(arr,0,size-1);
}

快速排序非递归写法

非递归的思路主要是以使用栈来模拟递归的过程

C语言不方便实现栈,这里选择使用C++和Java实现

C++:

#pragma once
#include<iostream>
#include<stack>
#include<vector>//快速排序
//传入数组,数组头,数组尾,三数取中,返回中间值的下标,数组区间[]形式
int GetMidIndex(std::vector<int>&arr, int left, int right) {int mid = (left + right) >> 1;if (arr[left] < arr[mid]) {if (arr[mid] < arr[right]) {//arr[left]<arr[mid]<arr[right]return mid;}else if (arr[right] > arr[left]) {//arr[right]<=arr[mid]//arr[left]<arr[right]<=arr[mid]return right;}else {//arr[right]<=arr[mid]//arr[right]<=arr[left]//arr[right]<=arr[left]<arr[mid]return left;}}else {//arr[left]>arr[mid]if (arr[mid] > arr[right]) {//arr[right]<arr[mid]<arr[left]return mid;}else if (arr[right] > arr[left]) {//arr[mid]<arr[left]<arr[right]return left;}else {//arr[mid]<arr[left]//arr[right]>=arr[mid]//arr[right]<=arr[left]//arr[mid]<arr[right]<arr[left]return right;}}
}
//让数组分成三部分,<key key >key,函数返回key值在数组的下标,方便调用方将数组分成两部分
//传入数组的区间为[]
int PrevNextPtr(std::vector<int>&arr, int left, int right) {int mid = GetMidIndex(arr, left, right);//key的下标std::swap(arr[left], arr[mid]);int keypos = left;int prev = left;int next = prev + 1;while (next <= right) {//因为数组区间为[],所以next=right时也要进行判断//next找比key值小的位置if (arr[keypos] > arr[next]) {prev++;if (prev != next) {std::swap(arr[prev], arr[next]);}}next++;}std::swap(arr[prev], arr[keypos]);return prev;
}void _QuickSortNotRecursive(std::vector<int>& vet, int left, int right) {//栈上的每个元素都是一对区间[left,right]std::stack<std::vector<int>>st;st.push({ left,right });while (!st.empty()) {std::vector<int>section = st.top(); st.pop();//[left,right]int keypos = PrevNextPtr(vet, section[0], section[1]);//将左右区间放入栈中if (section[0] < keypos-1) {//存在左区间,左区间入栈st.push({ section[0],keypos - 1 });}if (keypos + 1 < section[1]) {//存在右区间,有区间入栈st.push({ keypos + 1,section[1] });}}
}
void QuickSortNotRecursive(std::vector<int>& vet) {_QuickSortNotRecursive(vet, 0, vet.size() - 1);
}


Java:

//三种交换方法
private static int HoareLeftRightPtr(List<Integer>list,int left,int right){int midPos=GetMidPos(list,left,right);swap(list,left,midPos);int keyPos=left;int key=list.get(keyPos);while(left<right){while(list.get(right)>=key&&right>left){right--;}while(list.get(left)<=key&&left<right){left++;}swap(list,left,right);}swap(list,left,keyPos);return left;
}
//快速排序非递归
public static void QuickSortNotRecursive(List<Integer>list){Stack<List<Integer>>stack=new Stack<>();List<Integer>posits=new ArrayList<>(Arrays.asList(0,list.size()-1));stack.push(posits);while(!stack.isEmpty()){List<Integer>pos=stack.pop();int keyPos=HoareLeftRightPtr(list,pos.get(0),pos.get(1));if(pos.get(0)<keyPos-1){stack.push(new ArrayList<>(Arrays.asList(pos.get(0),keyPos-1)));}if(keyPos+1<pos.get(1)){stack.push(new ArrayList<>(Arrays.asList(keyPos+1,pos.get(1))));}}
}

性能与稳定性分析

快速排序:

  • 空间复杂度:递归的快速排序需要借助递归栈来保存必要的信息,借助的容量与递归深度相关,平均深度为O(logN)。最差情况递归深度为N最差空间复杂度为O(N)
  • 时间复杂度:如果每次分区间都很极端,左区间为0个,右区间为N-1个。此时时间复杂度为O(N^2)。如果能做到每次将区间近似均等分的话时间复杂度为O(N*logN)

稳定性:在进行交换的时候,值相同的元素在进行和key比较时,上面的三个交换函数会改变值的相对位置,所以快速排序不是稳定排序。

4. 选择排序

Ⅰ 简单选择排序

选择排序思路为先进行一次遍历,找到最大值和最小值,把最小值放到数组开头,最大值放到数组最后,然后逐步向数组中心靠拢,知道left>=right时停止。

注意:有时候选择排序只选择大数字,这里采用高效率的选择,每次选最大值和最小值。

//选择排序
//传入数组,以及数组大小
void SelectSort(int* arr, int size) {int left = 0; int right = size - 1;//这里为了高效,每次选择最大值和最小值放到数组两端while (left < right) {int maxpos = left; int minpos = left;for (int i = left; i <= right; i++) {//数组为[]型if (arr[i] < arr[minpos]) {minpos = i;}else if (arr[i] > arr[maxpos]) {maxpos = i;}}swap(&arr[left], &arr[minpos]);//注意如果最大值在最左边,交换后此位置放的是最小值,最大值的位置改变到原最小值位置,要更新一下坐标if (maxpos == left) {maxpos = minpos;}swap(&arr[right], &arr[maxpos]);left++; right--;}
}

性能与稳定性分析

选择排序:

  • 空间复杂度:内有使用额外空间O(1)
  • 时间复杂度:遍历N次,每次还要进行N此对比,时间复杂度为O(N^2)

稳定性:在进行交换的时候,值相同的元素A,B排序后。A先被交换,顺序变为B,A。所以选择排序不是稳定排序。

Ⅱ 堆排序

首先先把数组建堆,这里要从小到大排序,所以构造大堆。

建堆完成后堆顶数据最大,堆顶数据和数组最后一个数据交换,同时记录数组大小的end- -,在进行向下调整,找到剩下数据的最大值放堆顶。

//堆排序
//传入数组,从哪里开始调整堆,堆的大小
void AdjustDown(int* arr, int root, int capcity) {int child = 2 * root + 1;while (child < capcity) {if (child + 1 < capcity && arr[child] < arr[child + 1]) {child++;}if (arr[child] > arr[root]) {swap(&arr[child], &arr[root]);}else {break;}root = child;child = 2 * root + 1;}
}
//传入数组,及数组大小,从小到大,建立大堆
void HeapSort(int* arr, int size) {//建堆,从最后一颗子树开始建堆,建堆时间复杂度为O(N)for (int parent = (size - 1 - 1) / 2; parent >= 0; parent--) {AdjustDown(arr, parent, size);}int end = size;while (end > 0) {//一次调整对比logN次swap(&arr[0], &arr[end - 1]);end--;AdjustDown(arr, 0, end);}
}

性能与稳定性分析

堆排序:

  • 空间复杂度:内有使用额外空间O(1)
  • 时间复杂度:一共N个数据,每个数据交换完后都要进行调整,一次调整循环logN次所以时间复杂度为O(N*logN)。
  • 但是数组建堆时间复杂度为O(N)具体推导过程点击这里
    其使用的等差×等比求和公式证明

稳定性:因为每次交换堆顶数据不能保证相同的数字相对位置不发生改变。所以堆排序不稳定。

5. 归并排序(二路)

归并排序思路:

  • 先将数组分成两段,记作left段和right段
  • 将left段排成有序,将right段排成有序
  • 最后将left和right段排成有序.

注意:将left和right排成有序的时候需要借助一个辅助数组,将排序的值先写到这个数组中,最后再拷贝到原数组上。

建议将开辟的数组以参数的形式传递,避免每次递归都创建数组影响性能。

Ⅰ 归并排序递归写法

递归的过程类似二叉树的后序遍历,先把左子数组排有序,再把右子数组排成有序。最后把整体排成有序。

//归并排序
//传入数组,以及分割的两个数组的起始位置和最终位置,子数组范围[]
//递归到这里[leftBegin,leftEnd][rightBegin,rightEnd]是两个有序区间
//要让[leftBegin,rightEnd]变为有序
void Merge(int* arr, int* buff, int leftBegin, int leftEnd, int rightBegin, int rightEnd) {int buffpos = 0;//需要拷贝的数组下标范围int begin = leftBegin; int end = rightEnd;while (leftBegin <= leftEnd && rightBegin <= rightEnd) {if (arr[leftBegin] <= arr[rightBegin]) {buff[buffpos++] = arr[leftBegin++];}else {buff[buffpos++] = arr[rightBegin++];}}while (leftBegin <= leftEnd) {buff[buffpos++] = arr[leftBegin++];}while (rightBegin <= rightEnd) {buff[buffpos++] = arr[rightBegin++];}for (int i = begin; i <= end; i++) {arr[i] = buff[i - begin];}
}
void _MergeSort(int* arr, int* buff, int left, int right) {if (left >= right) {//递归结束条件return;}int mid = (left + right) >> 1;_MergeSort(arr, buff, left, mid);_MergeSort(arr, buff, mid + 1, right);Merge(arr, buff, left, mid, mid + 1, right);
}
//传入数组,数组大小
void MergeSort(int* arr, int size) {int* buff = (int*)malloc(size * sizeof(int));assert(buff);_MergeSort(arr, buff, 0, size - 1);free(buff);
}

Ⅱ 归并排序非递归写法

归并排序可以使用循环来实现,开始时每个子区间长度为1,之后子区间长度变为原来的2倍,继续归并。直到两个子区间包括整个数组就可以停止循环了。

//传入数组,以及分割的两个数组的起始位置和最终位置,子数组范围[]
//递归到这里[leftBegin,leftEnd][rightBegin,rightEnd]是两个有序区间
//要让[leftBegin,rightEnd]变为有序
void Merge(int* arr, int* buff, int leftBegin, int leftEnd, int rightBegin, int rightEnd) {int buffpos = 0;//需要拷贝的数组下标范围int begin = leftBegin; int end = rightEnd;while (leftBegin <= leftEnd && rightBegin <= rightEnd) {if (arr[leftBegin] <= arr[rightBegin]) {buff[buffpos++] = arr[leftBegin++];}else {buff[buffpos++] = arr[rightBegin++];}}while (leftBegin <= leftEnd) {buff[buffpos++] = arr[leftBegin++];}while (rightBegin <= rightEnd) {buff[buffpos++] = arr[rightBegin++];}for (int i = begin; i <= end; i++) {arr[i] = buff[i - begin];}
}
//归并排序非递归实现
//传入数组,数组大小
void MergeSortNotRecursion(int* arr, int size) {int* buff = (int*)malloc(size * sizeof(int));assert(buff);//<assert.h>int step = 1;//开始归并区间为1while (step < size) {for (int i = 0; i < size; i += 2 * step) {//左子数组[i,i+step-1]  右子数组[i+step,i+2*step-1]int leftbegin = i; int leftend = i + step - 1;int rightbegin = i + step; int rightend = i + 2 * step - 1;//右子数组可能不存在,即i+step>size,不需要继续这次归并了//右子数组可能存在一部分,即i+2*step-1>size,需要缩小右子数组的范围继续归并//左子数组可能存在一部分,即i+step-1>size,不需要继续这次归并了//左子数组一定存在。进入循环i < size一定成立if (rightbegin >= size) {//右子数组不存在break;}//右子数组存在if (rightend >= size) {// 右子数组存在一部分rightend = size - 1;//Merge传入数组范围是[]的}Merge(arr, buff, leftbegin, leftend, rightbegin, rightend);}step *= 2;}free(buff);
}

性能与稳定性分析

归并排序:

  • 空间复杂度:在和并两个子数组时需要开辟原数组长度的辅助空间,空间复杂度为O(N)
  • 时间复杂度:把归并的过程想象成二叉树,二叉树的高度为logN,每层都需要遍历一遍数组,所以时间复杂度为O(N*logN)

稳定性:值相同的元素在Merge函数中,进入额外开辟的数组顺序一定不会变,所以归并排序是稳定的排序。

6. 计数排序(哈希表排序)

思路:

  • 先遍历一遍数组,找到最大值和最小值

  • 新建一个数组count,它的大小是max-min+1

  • 开始将数组中的每一个元素都初始化成0,用来统计不同的数据出现的次数count[a[i]-min]++(这里采用了相对映射的思想)

  • 最后,遍历count数组,最先不为0的下标+min将其放到原数组的第一个位置,之后再以此遍历count数组即可。

//计数排序
//传入数组,数组大小
void CoutSort(int* arr, int size) {int maxVal = arr[0]; int minVal = arr[0];//遍历数组找最大值,最小值for (int i = 0; i < size; i++) {if (arr[i] < minVal) {minVal = arr[i];}else if (arr[i] > maxVal) {maxVal = arr[i];}}int capcity = maxVal - minVal + 1;//新开数组的大小int* buff = (int*)malloc(sizeof(int) * capcity);assert(buff);memset(buff, 0, sizeof(int) * capcity);for (int i = 0; i < size; i++) {buff[arr[i] - minVal]++;//统计数字出现的次数}int arrpos = 0;for (int i = 0; i < capcity; i++) {//将数据写回while (buff[i]--) {arr[arrpos++] = i + minVal;}}free(buff);
}

性能与稳定性分析

计数排序:

  • 空间复杂度:R是数组最大值-最小值,开辟了一个数组存放次数空间复杂度为O( R )
  • 时间复杂度:R是数组最大值-最小值,计数排序时间复杂度为O(2*N+R)

注意:这种排序只适合数据比较集中的情况,且数据是整数才可以使用。如果范围集中,效率很好

稳定性:采用计数器,可以使相同的值相对位置不变,是稳定的排序。

7. 基数排序(桶排序)

基数排序特别的排序算法,通常有两种

  1. 最高位优先法,按照关键字位权重递减依次划分位更小的子序列最后将子序列连接成有序的序列
  2. 最低位优先法,按照关键字位权重递增的依次划分子序列

eg:使用最低为优先法排序数组1,23,666,333,111,120,999,250,585

  1. 首先按照个位数0-9开辟10个和要排序的数组大小相同的数组。

  2. 数组的元素按照个位数对应到相应的桶中。

  3. 排列完后重新按照顺序写回数组中

  4. 按照十位数0-9开辟10个大小和待排数组大小相同的数组,按照十位数的0-9对应到相应的数组中。(没有十位数的按照十位数是0来处理)

  5. 排列完后重新按照顺序写回数组中

  6. 因为数组中的元素是3位数,所以还要按照百位0-9重复上面的过程,最后写回数组时数组就有序了

性能与稳定性分析

基数排序:

  • 时间复杂度:d趟分配收集,一次分配O( n ),一次收集O( r )

    总时间复杂度O(d(n+r))

    d是排序数字的位数,n是排序数字的长度,r 是开辟的数组的个数

  • 空间复杂度O( r )

基数排序按照位来排序,是稳定的

/*** @file BucketSort.h* @author your name (you@domain.com)* @brief* 1. 首先按照个位数0-9开辟10个和要排序的数组大小相同的数组。* 2. 数组的元素按照个位数对应到相应的桶中。* 3. 排列完后重新按照顺序写回数组中* 时间复杂度:d趟分配收集,一次分配O( n ),一次收集O( r )* 总时间复杂度O(d(n+r))* d是排序数字的位数,n是排序数字的长度,r 是开辟的数组的个数* 空间复杂度O( r )* 基数排序按照位来排序,是稳定的* @version 0.1* @date 2023-03-07** @copyright Copyright (c) 2023**/#include <iostream>
#include <vector>
#include <algorithm>using namespace std;// 获取val的位数
int digitPlace(int val)
{int ret = 0;while (val != 0){ret += 1;val /= 10;}return ret;
}void BucketSort(vector<int> &vet)
{// 获取最大位数int times = digitPlace(*std::max_element(vet.begin(), vet.end()));int flag = 1;for (int i = 0; i < times; i++){// 创建10个箱子, 初始化为 INT_MINvector<vector<int>> Bucket(10, vector<int>(vet.size(), INT_MIN));for (int pos = 0; pos < vet.size(); pos++){// 获取这一位的数字int idx = (vet[pos] / flag) % 10;Bucket[idx][pos] = vet[pos];}flag *= 10;int index = 0;for (int bucket = 0; bucket < 10; bucket++){for (int pos = 0; pos < vet.size(); pos++){if (Bucket[bucket][pos] != INT_MIN){vet[index++] = Bucket[bucket][pos];}}}}
}

王道书上使用一个数组,数组元素是链表的方式实现的桶排序,我这里用二维数组实现了,这种方法占用空间比挂链表大一些

8. 内部排序总结

排序方法 时间复杂度 空间复杂度 排序稳定性
插入排序 O(N^2) O(1) 稳定
折半插入排序 O(N^2) O(1) 稳定
希尔排序 O(N^2) O(1) 不稳定
冒泡排序 O(N^2) O(1) 稳定
快速排序 O(N*logN) O(logN~N) 不稳定
选择排序 O(N^2) O(1) 不稳定
堆排序 O(N*logN) O(1) 不稳定
归并排序 O(N*logN) O(N) 稳定
计数排序 O(N+K) O(N) 稳定
基数排序 O(d(N+R)) O( R ) 稳定

9. 内部排序与外部排序

内部排序:数据量较少,可以放到内存中排序。
外部排序:数据量较大,内存放不下。数据放到磁盘文件中。

其中,上面的所有排序,除了归并排序不仅适合内部排序,同时适应外部排序。其他的所有排序只适合内部排序。

归并排序将文件切分成多份。文件变小后,每次读取两个小文件的一个数字,将排序的结果写到新文件中。就类似归并的非递归形式继续读取即可将大文件进行排序。

附录

C++实现上述排序所有细节

#pragma once
#include<vector>
#include<iostream>
#include<algorithm>
#include<stack>void PrintVetor(std::vector<int>& vet) {for (auto num : vet) {std::cout << num << " ";}std::cout << std::endl;
}//从小到大排序
class Sort {public://直接插入排序static void InsertSort(std::vector<int>&vet) {for (int i = 0; i < vet.size() - 1; i++) {int endpos = i;int next = vet[endpos + 1];while (endpos >= 0) {if (vet[endpos] > next) {vet[endpos + 1] = vet[endpos];endpos--;}else {break;}}vet[endpos + 1] = next;}}//折半插入排序static void InsertSortUseBisearch(std::vector<int>& vet) {for (int i = 0; i < vet.size() - 1; i++) {int endpos = i;int next = vet[endpos + 1];std::vector<int>::iterator findpos = std::upper_bound(vet.begin(), vet.begin() + endpos + 1, next); //二分查找找第一个大于next位置for (auto pos = vet.begin() + endpos; pos >= findpos; pos--) {if (pos == vet.begin()) {//C++迭代器不能为负数,pos指向头时就是最后一次拷贝*(pos + 1) = *pos;break;}*(pos + 1) = *pos;}*findpos = next;}}//希尔排序static void ShellSort(std::vector<int>& vet) {int step = vet.size();while (step > 1) {step = step / 3 + 1;for (int i = 0; i < vet.size() - step; i++) {int endpos = i;int next = vet[endpos + step];while (endpos >= 0) {if (vet[endpos] > next) {vet[endpos + step] = vet[endpos];endpos -= step;}else {break;}}vet[endpos + step] = next;}}}//冒泡排序static void BubbleSort(std::vector<int>& vet) {for (int i = 0; i < vet.size(); i++) {bool flag = false;//标记是否发生交换for (int j = 0; j < vet.size() - i - 1; j++) {if (vet[j] > vet[j + 1]) {flag = true;std::swap(vet[j], vet[j + 1]);}}if (flag == false) {break;}}}//快速排序
private://key值的选取(三数取中)static int GetMidPos(std::vector<int>& vet, int leftpos, int rightpos) {int midpos = (leftpos + rightpos) >> 1;if (vet[leftpos] < vet[midpos]) {if (vet[midpos] < vet[rightpos]) {return midpos;}else if (vet[rightpos] > vet[leftpos]) {return rightpos;}else {return leftpos;}}else {if (vet[rightpos] < vet[midpos]) {return midpos;}else if (vet[leftpos] < vet[rightpos]) {return leftpos;}else {return rightpos;}}}//三种交换方法,函数返回key值在数组的下标//前后指针法static int HoareLeftRightPtr(std::vector<int>& vet, int left, int right) {int midpos = GetMidPos(vet, left, right);std::swap(vet[midpos], vet[left]);//把key值放到数组最左边int keypos = left; int key = vet[keypos];while (left < right) {while (vet[right] >= key && right > left) {right--;}while (vet[left] <= key && left < right) {left++;}std::swap(vet[left], vet[right]);}std::swap(vet[left], vet[keypos]);return left;}//挖坑法static int PitExchangeMethod(std::vector<int>& vet, int left, int right) {int midpos = GetMidPos(vet, left, right);std::swap(vet[left], vet[midpos]);int key = vet[left];//开始left为坑while (left < right) {while (vet[right] >= key && left < right) {right--;}vet[left] = vet[right];while (vet[left] <= key && left < right) {left++;}vet[right] = vet[left];}//left位置一定为坑vet[left] = key;return left;}//前后指针法static int PrevNextPtr(std::vector<int>& vet, int left, int right) {int midpos = GetMidPos(vet, left, right);std::swap(vet[left], vet[midpos]);int keypos = left;int prev = left;int next = prev + 1;//找小while (next <= right) {if (vet[next] < vet[keypos]) {prev++;if (prev != next) {std::swap(vet[prev], vet[next]);}}next++;}std::swap(vet[prev], vet[keypos]);return prev;}static void _QuickSort(std::vector<int>& vet, int left, int right) {if (left >= right) {return;}/*int keypos = PrevNextPtr(vet, left, right);*//*int keypos = HoareLeftRightPtr(vet, left, right);*/int keypos = PitExchangeMethod(vet, left, right);_QuickSort(vet, left, keypos - 1);_QuickSort(vet, keypos + 1, right);}
public:static void QuickSort(std::vector<int>& vet) {_QuickSort(vet, 0, vet.size() - 1);}//快速排序非递归static void QuickSortNotRecursive(std::vector<int>& vet) {//栈上的每个元素都是一对区间[left,right]std::stack<std::vector<int>>st;st.push({ 0,static_cast<int>(vet.size() - 1) });while (!st.empty()) {std::vector<int>range = st.top(); st.pop();int keypos = HoareLeftRightPtr(vet, range[0], range[1]);if (range[0] < keypos - 1) {st.push({ range[0],keypos - 1 });}if (keypos + 1 < range[1]) {st.push({ keypos + 1,range[1] });}}}//选择排序static void SelectSort(std::vector<int>& vet) {int left = 0; int right = vet.size() - 1;while (left < right) {int maxpos = left; int minpos = left;for (int i = left; i <= right; i++) {if (vet[i] > vet[maxpos]) {maxpos = i;}else if (vet[i] < vet[minpos]) {minpos = i;}}std::swap(vet[left], vet[minpos]);if (maxpos == left) {maxpos = minpos;}std::swap(vet[maxpos], vet[right]);left++; right--;}}//堆排序,从小到大排序,建立大堆
private:static void AdjustDown(std::vector<int>& vet, int parent, int capcity) {int child = 2 * parent + 1;while (child < capcity) {if (child + 1 < capcity && vet[child + 1] > vet[child]) {child++;}if (vet[parent] < vet[child]) {std::swap(vet[parent], vet[child]);}else {break;}parent = child;child = 2 * parent + 1;}}
public:static void HeapSort(std::vector<int>&vet) {//建堆for (int i = (vet.size() - 1 - 1) / 2; i >= 0; i--) {AdjustDown(vet, i,vet.size());}int end = vet.size();while (end > 0) {std::swap(vet[0], vet[end - 1]);end--;AdjustDown(vet, 0, end);}}//归并排序
private:static void Merge(std::vector<int>& vet, std::vector<int>& buff, int leftfirst, int leftend, int rightfirst, int rightend) {int buffpos = 0;int begin = leftfirst; int end = rightend;while (leftfirst <= leftend && rightfirst <= rightend) {if (vet[leftfirst] <= vet[rightfirst]) {buff[buffpos++] = vet[leftfirst++];}else {buff[buffpos++] = vet[rightfirst++];}}while (leftfirst <= leftend) {buff[buffpos++] = vet[leftfirst++];}while (rightfirst <= rightend) {buff[buffpos++] = vet[rightfirst++];}for (int i = begin; i <= end; i++) {vet[i] = buff[i - begin];}}static void _MergeSort(std::vector<int>& vet, std::vector<int>buff, int left, int right) {//[]if (left >= right) {return;}int mid = (left + right) >> 1;_MergeSort(vet, buff, left, mid);_MergeSort(vet, buff, mid + 1, right);Merge(vet, buff, left, mid, mid + 1, right);}
public:static void MergeSort(std::vector<int>& vet) {std::vector<int>buff(vet.size());_MergeSort(vet, buff, 0, vet.size() - 1);}//归并排序非递归static void MergeSortNotRecursion(std::vector<int>& vet) {int step = 1;std::vector<int>buff(vet.size());while (step < vet.size()) {for (int i = 0; i < vet.size(); i += 2 * step) {int leftfirst = i; int leftend = i + step - 1;int rightfirst = i + step; int rightend = i + 2 * step - 1;if (rightfirst >= vet.size()) {//没有右区间停止归并break;}if (rightend >= vet.size()) {rightend = vet.size() - 1;}Merge(vet, buff, leftfirst, leftend, rightfirst, rightend);}step *= 2;}}//计数排序static void CoutSort(std::vector<int>& vet) {int max = *std::max_element(vet.begin(), vet.end());int min = *std::min_element(vet.begin(), vet.end());std::vector<int>count(max - min + 1, 0);for (int i = 0; i < vet.size(); i++) {count[vet[i] - min]++;}int vetpos = 0;for (int i = 0; i < count.size(); i++) {while (count[i]--) {vet[vetpos++] = i + min;}}}
};

Java实现上述排序所有细节

import java.util.*;public class JavaSort {//直接插入排序public static void InsertSort(List<Integer>list){for(int i=0;i<list.size()-1;i++){int endPos=i;int next=list.get(endPos+1);while(endPos>=0){if(list.get(endPos)>next){list.set(endPos+1, list.get(endPos));endPos--;}else{break;}}list.set(endPos+1,next);}}//折半插入排序[]private static int BinarySearch(List<Integer> list,int target,int left,int right){while(left<=right){int mid=(left+right)>>1;if(list.get(mid)>target){right=mid-1;}else if(list.get(mid)<target){left=mid+1;}else{//相同,保证稳定性left=mid+1;break;}}return left;}public static void InsertSortUseBisearch(List<Integer>list){for(int i=0;i<list.size()-1;i++){int endPos=i;int next=list.get(endPos+1);int findPos=BinarySearch(list,next,0,endPos);for(int pos=endPos;pos>=findPos;pos--){list.set(pos+1,list.get(pos));}list.set(findPos,next);}}//希尔排序public static void ShellSort(List<Integer>list){int step=list.size();while(step>1){step=step/3+1;for(int i=0;i< list.size()-step;i++){int endPos=i;int next= list.get(i+step);while(endPos>=0){if(list.get(endPos)>next){list.set(endPos+step,list.get(endPos));endPos-=step;}else{break;}}list.set(endPos+step,next);}}}//冒泡排序private static void swap(List<Integer>list,int left,int right){int temp=list.get(left);list.set(left, list.get(right));list.set(right,temp);}public static void BubbleSort(List<Integer>list){for(int i=0;i< list.size();i++){boolean flag=false;for(int j=0;j< list.size()-i-1;j++){if(list.get(j)>list.get(j+1)){flag=true;swap(list,j,j+1);}}if(!flag){break;}}}//快速排序//三数取中,返回中间值的下标private static int GetMidPos(List<Integer>list,int left,int right){int mid=(left+right)>>1;if(list.get(left)<list.get(mid)){if(list.get(mid)<list.get(right)){return mid;}else if(list.get(right)>list.get(left)){return right;}else{return left;}}else{if(list.get(mid)>list.get(right)){return mid;}else if(list.get(right)>list.get(left)){return left;}else{return right;}}}//三种交换方法private static int HoareLeftRightPtr(List<Integer>list,int left,int right){int midPos=GetMidPos(list,left,right);swap(list,left,midPos);int keyPos=left;int key=list.get(keyPos);while(left<right){while(list.get(right)>=key&&right>left){right--;}while(list.get(left)<=key&&left<right){left++;}swap(list,left,right);}swap(list,left,keyPos);return left;}private static int PitExchangeMethod(List<Integer>list,int left,int right){int midPos=GetMidPos(list,left,right);swap(list,left,midPos);int key=list.get(left);while(left<right){while(list.get(right)>=key&&right>left){right--;}list.set(left,list.get(right));while(list.get(left)<=key&&left<right){left++;}list.set(right, list.get(left));}list.set(left,key);//left位置一定时坑return left;}private static int PrevNextPtr(List<Integer>list,int left,int right){int mid=GetMidPos(list,left,right);swap(list,left,mid);int keyPos=left; int key=list.get(keyPos);int prev=left; int next=left+1;//找小与key值while(next<list.size()){if(list.get(next)<key){prev++;if(prev!=next){swap(list,prev,right);}}next++;}swap(list,keyPos,prev);return prev;}private static void _QuickSort(List<Integer>list,int left,int right){if(left>=right){return;}//int keyPos=HoareLeftRightPtr(list,left,right);//int keyPos=PitExchangeMethod(list, left, right);int keyPos=PrevNextPtr(list, left, right);_QuickSort(list,left,keyPos-1);_QuickSort(list,keyPos+1,right);}public static void QuickSort(List<Integer>list){_QuickSort(list,0,list.size()-1);}//快速排序非递归public static void QuickSortNotRecursive(List<Integer>list){Stack<List<Integer>>stack=new Stack<>();List<Integer>posits=new ArrayList<>(Arrays.asList(0,list.size()-1));stack.push(posits);while(!stack.isEmpty()){List<Integer>pos=stack.pop();int keyPos=HoareLeftRightPtr(list,pos.get(0),pos.get(1));if(pos.get(0)<keyPos-1){stack.push(new ArrayList<>(Arrays.asList(pos.get(0),keyPos-1)));}if(keyPos+1<pos.get(1)){stack.push(new ArrayList<>(Arrays.asList(keyPos+1,pos.get(1))));}}}//选择排序public static void SelectSort(List<Integer>list){int left=0; int right=list.size()-1;while(left<right){int maxPos=left;int minPos=left;for(int i=left;i<=right;i++){if(list.get(i)<list.get(minPos)){minPos=i;}else if(list.get(i)>list.get(maxPos)){maxPos=i;}}swap(list,left,minPos);if(maxPos==left){maxPos=minPos;}swap(list,right,maxPos);left++;right--;}}//堆排序,从小到大排序,建立大堆private  static void AdjustDown(List<Integer>list,int parent,int useSize){int child=parent*2+1;while(child<useSize){if(child+1<useSize&&list.get(child)<list.get(child+1)){child++;}if(list.get(child)>list.get(parent)){swap(list,child,parent);}else{break;}parent=child;child=2*parent+1;}}public static void HeapSort(List<Integer>list){//建堆for(int i= (list.size()-1-1)/2;i>=0;i--){AdjustDown(list,i,list.size());}int end= list.size();while(end>0){swap(list,0,end-1);end--;AdjustDown(list,0,end);}}//归并排序private  static void Merge(List<Integer>list,List<Integer>buff,int leftBegin,int leftEnd,int rightBegin,int rightEnd){buff.clear();int begin=leftBegin;int end=rightEnd;while(leftBegin<=leftEnd&&rightBegin<=rightEnd){if(list.get(leftBegin)<=list.get(rightBegin)){buff.add(list.get(leftBegin++));}else{buff.add(list.get(rightBegin++));}}while(leftBegin<=leftEnd){buff.add(list.get(leftBegin++));}while(rightBegin<=rightEnd){buff.add(list.get(rightBegin++));}for(int i=begin;i<=end;i++){list.set(i,buff.get(i-begin));}}private static void _MergeSort(List<Integer>list,List<Integer>buff,int left,int right){if(left>=right){return;}int midPos=(left+right)>>1;_MergeSort(list,buff,left,midPos);_MergeSort(list,buff,midPos+1,right);Merge(list,buff,left,midPos,midPos+1,right);}public static void MergeSort(List<Integer>list){List<Integer>buff=new ArrayList<>(list.size());_MergeSort(list,buff,0,list.size()-1);}//归并排序非递归写法public static void MergeSortNotRecursion(List<Integer>list){int step=1;List<Integer> buff = new ArrayList<>();while(step<list.size()){for(int i=0;i<list.size();i+=2*step){int leftBegin=i; int leftEnd=i+step-1;int rightBegin=i+step; int rightEnd=i+2*step-1;if(rightBegin>=list.size()){break;}if(rightEnd>= list.size()){rightEnd= list.size()-1;}Merge(list,buff,leftBegin,leftEnd,rightBegin,rightEnd);}step*=2;}}//计数排序public static void CoutSort(List<Integer>list){int max= Collections.max(list);int min=Collections.min(list);int[]array=new int[max-min+1];for (Integer integer : list) {array[integer - min]++;}int arrPos=0;for(int i=0;i< array.length;i++){while((array[i]--)>0){list.set(arrPos++,i+min);}}}
}

代码实现位置

王道考研数据结构所有代码仓库

数据结构-考研难点代码突破(C/C++/Java排序算法,性能及其稳定性分析(内部排序))相关推荐

  1. 数据结构-考研难点代码突破 (C++实现有向无环图的拓扑排序)

    文章目录 1. AOV网 2. 拓扑排序 C++代码 1. AOV网 AOV网∶若用DAG 图(有向无环图)表示一个工程,其顶点表示活动,用有向边<Vi,Vj>表示活动 Vi必须先于活动V ...

  2. Java 与排序算法(2):选择排序

    一.选择排序 选择排序(Selection Sort)是一种简单的排序算法,其基本思想是在待排序序列中选择最小(或最大)的元素,将其与序列的第一个元素交换位置,然后在剩余的元素中继续选择最小(或最大) ...

  3. Java GC算法——日志解读与分析(GC参数基础配置分析)

    可以先阅读前篇:Java GC算法背景原理与内存池划分,对于内存池的划分先有个概念后再来看各个部分的垃圾回收详情会更好 文章目录 1. 触发GC的示例代码 2. 常见的GC日志参数 2.1 输出日志详 ...

  4. 高斯模糊java代码_简单的java高斯模糊算法

    importjava.awt.Color;importjava.awt.image.BufferedImage;importjava.io.File;importjava.io.IOException ...

  5. java 线性的排序算法_077-线性查找分析和实现

    2.网上数据结构和算法的课程不少,但存在两个问题: 1)授课方式单一,大多是照着代码念一遍,数据结构和算法本身就比较难理解,对基础好的学员来说,还好一点,对基础不好的学生来说,基本上就是听天书了 2) ...

  6. Java排序算法——冒泡排序 及其稳定性和时间复杂度

    冒泡排序(Bubble Sort) 它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序错误就把他们交换过来.走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成 ...

  7. 南京邮电考研计算机科学大纲,2019年南京邮电大学811数据结构考研大纲

    811--<数据结构>考研大纲 一.考核内容: 1 绪论 1.1算法的基本概念 1.2数据结构的基本概念 1.3数据抽象和抽象数据类型 1.4描述数据结构和算法 1.5算法分析的基本方法 ...

  8. python 按条件选择行和列数据_小白学数据结构-排序算法Python(冒泡、选择、快速、希尔等等)...

    排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存. 我们通常所说的排序算法往往指的是内部排序算法,即数据 ...

  9. 数据结构实验8:内部排序

    实验8                                                                            姓名: 学号: 班级: 8.1 实验目的 ...

最新文章

  1. 深度学习英文文献_文献速递 | 预测术后30天死亡率的深度学习模型
  2. android edittext seterror,EditText之setError方法一二
  3. 【Presto】http-worker-103 com.facebook.presto.execution.SqlTaskManager Switching coordinator affinity
  4. 从@ComponentScan注解配置包扫描路径到IoC容器中的BeanDefinition,经历了什么(三)?
  5. tpadmin文档 框架更新
  6. PHP获取服务器图片并添加水印
  7. 忘了她,就像忘了一朵花
  8. 用微笑面对一切令你烦恼的人或事,不久之后,你会发现令你微笑的人或事变得越来越多了。
  9. JAVA超全笔试/面试考试题.(500问)--第三章面试题全面收录
  10. 计算机学生要学的基础知识,中小学生应注重学习计算机的基础知识
  11. Unity导表工具Luban插件的数据加载原理与优化
  12. 技术总结-技术方案模板
  13. Pytorch 叶子张量 leaf tensor (叶子节点) (detach)
  14. 【转】Mosquitto持久层群推消息实现思路
  15. Flappy Bird背后的故事
  16. krita 平涂 线稿上色 颜色蒙版
  17. 华为员工详述华为加班内幕 在华为不加班不现实
  18. 卫星星座介绍及系统参数:Starlink星座
  19. MATLAB算法实战应用案例精讲-【智能优化算法】 基于帕累托包络的选择算法II(PESA-II)(附MATLAB代码实现)
  20. 网页自适应手机、电脑屏幕的设置方法

热门文章

  1. Pr 入门教程:如何录制画外音?
  2. 2022年山东省安全员B证判断题及模拟考试
  3. ubuntu18系统修改ip、单网卡配置多ip
  4. TTI bundling
  5. http返回码401
  6. Angular 热替换HMR 报错cannot find name module
  7. 新产品·新技术·新领域 LabVIEW能为我们做什么
  8. 爬虫第一弹——爬取京东手机信息
  9. 第 1 章 单片机概述
  10. uniapp顶部安全距离(包括app)