赞
踩
数组中相同的值,在排序之后相对位置是否发生变化,如果会改变,就不稳定,能保证不变就是稳定
OJ测试链接:https://leetcode.cn/problems/sort-an-array/
核心思想:把后一个数插入到前面的有序区间,使得整体有序
比如说:[0,end]
是有序的区间,x是end的下一个位置的元素,此时要做的就是将x插入到这个有序区间,使得[0,x]
变有序
[0,end]
区间找到一个符合x插入的位置,然后插入到对应位置void InsertSort(vector<int>& v) { int n = v.size(); for(int i = 0;i<n-1;i++) //注意:i的范围是[0,n-2] { //思想:将x插入到[0,end]的有效区间 int end = i; int x = v[end+1]; while(end >= 0) //找到合适的位置插入 { if(v[end] > x) { v[end+1] = v[end]; //当前元素往后挪 end--;//下一轮判断,前一个数和x比较 } else break;//找到插入位置了 } v[end + 1] = x;//插入到此时的end位置的后面 } }
最坏情况(数组元素逆序):每次插入,元素都要往后移动
最好情况:(接近有序或者有序),基本不用移动数据 ->O(N)
空间复杂度:O(1)
稳定性:它是一种稳定的排序算法,因为相同元素比较时,我们是插入到它的后面
稳定性:稳定
[0,end]
的有序区间的时候,当区间元素和x的值相同的时候,是将x插入到该区间元素后面,二者的相对顺序不变基本思想是:
1.先选定一个gap,把待排序数据分组,所有距离为gap分在同一组内,并对每一组内的记录进行排序。
2.然后取重复上述分组和排序的工作,当到达gap=1时,就是直接插入排序,整体就有序了
gap越大:预排序越快,预排序后越不接近有序
gap越小:预排序越慢,预排序后越接近有序
什么时候预排序的效果最好:数据是逆序的时候,预排序完成就接近有序
时间复杂度:O(N^1.3)
稳定性:不稳定
写法1:多组一起预排序
void ShellSort(vector<int>& v) { int n = v.size(); int gap = n; //gap为几就分为几组, 预排序 while(gap > 1) { //目的是为了保证最后能让gap为1,进行直接插入排序 gap = gap / 3 + 1; //或者:gap = gap / 2 //写法1:一锅炖 for(int i = 0 ;i < n - gap;i++) //注意:i的范围! 否则end+gap会越界 { int end = i;//end的范围:[0,n- gap -1] int x = v[end + gap];//i的范围:[gap,n - 1] while(end >= 0) { if(v[end] > x) { v[end + gap] = v[end];//把a[end]往后移动,以gap为间隔的为一组,所以移动到a[end+gap]位置 end -= gap;//下一轮循环,以gap为间隔的为一组,前一个数(end-gap位置对应的值)和x比较 } else break; } v[end + gap] = x;//以gap为间隔的为一组,把x放在end + gap位置 } } }
写法2:每一组分别进行预排序
需要变动的位置:循环gap次,每次处理一组!
+=gap
void ShellSort(vector<int>& v) { int n = v.size(); int gap = n; //gap为几就分为几组, 预排序 while(gap > 1) { //目的是为了保证最后能让gap为1,进行直接插入排序 gap = gap / 3 + 1; //或者:gap = gap / 2 //gap组,每组单独排序 for(int j = 0;j<gap;j++) { for(int i = j ;i < n - gap;i+=gap) //注意:i的初始值!!和变动范围 i+=gap { int end = i;//end的范围:[0,n- gap -1] int x = v[end + gap];//i的范围:[gap,n - 1] while(end >= 0) { if(v[end] > x) { v[end + gap] = v[end];//把a[end]往后移动,以gap为间隔的为一组,所以移动到a[end+gap]位置 end -= gap;//下一轮循环,以gap为间隔的为一组,前一个数(end-gap位置对应的值)和x比较 } else break; } v[end + gap] = x;//以gap为间隔的为一组,把x放在end + gap位置 } } } }
**思想:**每次从要排序的区间当中找到最大和最小的数,如果是排序,那么把他区间的最大的数和区间右端点对应值交换,把区间中最小的数和区间左端点对应值交换,然后缩小区间重复上述步骤,直到区间只有一个数
时间复杂度:遍历一遍才能选出一个数或者两个数,无论什么情况都是O(N^2)
稳定性:不稳定
方法1:每次选择一个数
void SelectSort(vector<int>& v) { int n = v.size(); int end = n-1; while(end > 0)//当区间只有一个元素就不需要选了所以循环条件为:end > 0 { int maxIndex = 0; //从[0,end]区间选取一个数放到end位置 for(int i = 0;i<=end;i++) { if(v[i] > v[maxIndex]) //更新最大值所在位置的下标 maxIndex = i; } ::swap(v[end],v[maxIndex]); end--;//缩小区间 } }
方法2:每次选择两个数
void SelectSort(vector<int>& v) { int n = v.size(); int begin = 0,end = n -1; while(begin < end) { //在[begin,end]区间找出最大最小的位置 int maxIndex = begin,minIndex = begin; for(int i = begin;i<=end;i++) { if(v[i] > v[maxIndex]) maxIndex = i; if(v[i] < v[minIndex]) minIndex = i; } ::swap(v[begin],v[minIndex]);//最小值放到begin位置 //坑点:如果begin和maxIndex一样 //因为下面一步begin位置和值已经和minIndex位置交换了,所以就导致了minIndex位置放的才是最大值了 //所以需要特判一下,如果begin和maxIndex相同,那么经过上面一步交换之后,minIndex位置放的才是最大值 if(begin == maxIndex) maxIndex = minIndex; ::swap(v[end],v[maxIndex]);//最大值放到end位置 begin++,end--;//缩小区间 } }
排升序 建大堆 排降序 建小堆
建堆的方法:
向上调整:数组的第一个元素认为是堆,然后从第二个元素开始,把数组的每个元素插入堆中,然后向上调整
向下调整:从最后一个叶子结点的父亲开始调整
1.首先需要先建堆,只需要从最后一个叶子结点的父节点开始,在数组当中从后往前去向下调整即可
2.建好堆之后,将堆顶数据与堆的最后一个数据交换,然后对根位置进行一次堆的向下调整,但是调整时被交换到最后的那个数不参与向下调整,然后缩小堆中有效数据个数,剩下的元素进行向下调整,其余数又成一个大堆…重复上述步骤,直到堆中只剩下一个元素
时间复杂度分析:无论哪种方法建堆:都是O(N*logN)
稳定性:不稳定
//排升序 建大堆 void AdjustDown(vector<int>& v,int parent,int n) //从哪个位置向下调整,堆中有效元素个数 { int child = parent * 2 + 1; //左孩子 while(child < n) //最坏情况:调整到叶子节点 { if(child + 1 < n && v[child + 1] > v[child]) //选出较大的孩子 child += 1; if(v[child] > v[parent]) { ::swap(v[child],v[parent]); //向下迭代 parent = child; child = parent*2+1; } else break; //已经是大堆了 } } void HeapSort(vector<int>& v) { int n = v.size(); //1.从最后一个节点(下标:n-1)的父节点((child - 1 )/ 2)开始建堆(向下调整建堆) for(int i = (n - 1 - 1 )/ 2;i>=0;i--) AdjustDown(v,i,n); //2.调堆 int end = n-1;//认为是堆中有效元素的个数 & 当前堆顶元素应该交换之后放的位置 while(end > 0) { ::swap(v[end],v[0]); AdjustDown(v,0,end);//从根节点开始向下调整,认为堆中元素个数为end个 end--; } }
如果使用的是向上调整建堆:
//排升序 建大堆 void AdjustUp(vector<int>& v,int child) { int parent = (child - 1) / 2; while(parent >= 0) //最坏情况:调整到根节点(根节点可能也要和其孩子交换,所以条件是>=) { if(v[child] > v[parent]) { ::swap(v[child],v[parent]); //向上迭代 child = parent; parent = (child - 1 ) / 2; } else break;//堆已经构建好了 } } //建堆 //1.第一个元素认为是堆,然后从第二个元素开始每个元素都进行向上调整 for(int i = 1; i<n;i++) AdjustUp(v,i);
主要思想:相邻元素之间进行比较交换
假设排升序,一趟冒泡可以排序一个数,使最大的元素沉到最后面,那么下一轮排序就可以不比较已经排好序的元素
优化:如果提前有序了(某一趟冒泡当中没有元素交换),就不需要再冒泡了
时间复杂度:
稳定性:稳定
void BubbleSort(vector<int>& v) { int n = v.size(); //每一趟可以确定一个元素到准确位置,n个元素只需要进行n-1趟 for(int i = 0;i<n-1;i++) { bool flag = true;//是否已经有序 //每一趟都可以少比较一个已经确定好的数 for(int j = 0;j<n - 1 - i;j++) //注意:j<n-1-i { if(v[j] >v[j+1]) { ::swap(v[j],v[j+1]); flag = false; } } if(flag) break;//如果没有进入交换,就是说明已经有序了 } }
思想:取待排序区间上的某一个元素作为基准值,根据处理方法,将待排序区间上的元素划分为:左区间的元素小于基准值,右区间的元素大于基准值,然后对左右区间重复这个过程,直到所有元素都排列在相应位置上为止
时间复杂度分析:
O(N*logN)
每一层的所有左区间和右区间的单趟加起来,共处理N个元素 ,递归高度为logN 每一层处理N个元素 ,所以复杂度为N*logN
O(N^2)
因为有序时:选取的key都是左边(右边)的元素,那么每一趟排序的元素个数呈现等差数列
并且有可能递归层次太深,导致栈溢出
解决办法:
空间复杂度分析:
快排空间复杂度:主要是递归调用栈所占用的空间,与递归深度成线性关系。
最好情况(递归树最平衡):O(log n)
平均情况:O(log n)
最坏情况(递归树最高):O(n)
稳定性:不稳定
注意1:若选左边的值作为key,右边的先走。若选右边的值作为key,左边的先走
int process(vector<int>& v,int left,int right) { int keyi = left;//左边的作为key 那么右边先走 while(left < right) { //右往左走 找严格小的 while(left < right && v[right] >= v[keyi]) right--; //左往右走 找严格大的 while(left < right && v[left] <= v[keyi]) left++; if(left < right) ::swap(v[right],v[left]); } ::swap(v[left],v[keyi]); return left; } void QucikSort(vector<int>& v,int left,int right) { if(left >= right) return ; ::swap(v[left],v[left + rand() % (right - left + 1)]);//取左边位置作为基准值 ==> 本质是随机选key int keyi = process(v,left,right); //[left,keyi-1] keyi[keyi+1,right] QucikSort(v,left,keyi-1); QucikSort(v,keyi+1,right); }
注意:最初要保存的key是左边元素的值,而不是下标!因为可能会被覆盖
int process(vector<int>& v,int left,int right) { int keyi = v[left];//左边的作为key 那么右边先走 int pivot = left;//坑位 while(left < right) { //右往左走 找严格小的 while(left < right && v[right] >= keyi) right--; ::swap(v[right],v[pivot]); pivot = right;//更新坑位 //左往右走 找严格大的 while(left < right && v[left] <= keyi) left++; ::swap(v[left],v[pivot]); pivot = left;//更新坑位 } v[pivot] = keyi; return pivot; } void QucikSort(vector<int>& v,int left,int right) { if(left >= right) return ; ::swap(v[left],v[left + rand() % (right - left + 1)]);//取左边位置作为基准值 ==> 本质是随机选key int keyi = process(v,left,right); //[left,keyi-1] keyi[keyi+1,right] QucikSort(v,left,keyi-1); QucikSort(v,keyi+1,right); }
划分为:<=基准值的放左边,>基准值的放右边
做法:定义一个变量lessEqual
表示<=基准值的区域,最初初始化为left-1
,然后cur
从left
位置开始往后遍历,遍历[left,right-1]
区间(因为以最右位置作为基准值)
<=
区域的下一个数交换,然后<=区域
向右扩展,当前数跳向下一个数[left,lessEqual)
表示<=基准值的区域 最后让基准值和lessEqual+1位置交换,然后返回该位置int process(vector<int>& v,int left,int right) { int lessEqual = left - 1;//表示<=基准值的区域 int cur = left; //遍历[left,right-1]区间,以右边作为基准值 while(cur < right) { if(v[cur] <= v[right]) swap(v[++lessEqual],v[cur]); cur++; //不管怎么样,cur都要往右走 } //[left,lessEqual)表示<=基准值的区域 ::swap(v[++lessEqual],v[right]); return lessEqual; } void QucikSort(vector<int>& v,int left,int right) { if(left >= right) return ; ::swap(v[right],v[left + rand() % (right - left + 1)]); //取右边位置作为基准值 ==> 本质是随机选key int keyi = process(v,left,right); //[left,keyi-1] keyi[keyi+1,right] QucikSort(v,left,keyi-1); QucikSort(v,keyi+1,right); }
当所有元素都一样时:快速排序非常慢->O(N^2),此时加了三数取中也不行 或者类似是2 3 2 3 2 3 的数据
目标:<x的放左边,=x的放中间,>x的放右边
定义两个变量:less
表示<=x的区域,more
表示>x的区域。以最右边作为基准值,最初less初始化为:left more初始化为right
从left位置开始往后遍历,直到与more位置相遇
步骤:
//作用:选取[left,right]的一个数为基准值 //然后把这个区间内的元素划分为: 左边<pivot 中间=pivot 右边>pivot int* process(vector<int>& v,int left,int right,int pivot) { int less = left;// <pivot的区域 [left,less) int more = right;// >pivot的区域 (more,right] int cur = left; //从left位置开始往后走,直到和more相遇,more位置的值也需要考察 while(cur <= more) { if(v[cur] > pivot) ::swap(v[more--],v[cur]); //当前数换到大于区域当中,还要继续考察换过来的这个数 else if(v[cur] < pivot) ::swap(v[less++],v[cur++]);//当前数换到小于区域当中,然后cur往后走 else cur++; } //[left,less) [less,more] (more,right] return new int[2]{less,more}; } void QucikSort(vector<int>& v,int left,int right) { if(left >= right) return ; ::swap(v[right],v[left + rand() % (right - left + 1)]); int* equal = process(v,left,right,v[right]); //[left,equal[0]-1] [euqal[0],equal[1]] [euqal[1]+1,right] QucikSort(v,left,equal[0]-1); QucikSort(v,equal[1]+1,right); }
小区间优化:当分割到小区间时:不再采用递归的方法让这段子区间有序, 减少递归次数
如果区间内的元素小于10了,就不使用快排进行排序,因为这段区间已经接近有序了,使用直接插入排序进行排序这区间的元素
void InsertSort_QuickSort(vector<int>& v,int left,int right) { for(int i = left;i<right;i++) { int end = i;//end的范围:[left,right-1] int x = v[end+1];//将x插入到[0,end]的有序区间 while(end >= left) //注意:这里最多移动到left!!!并不是0 { if(v[end] > x) { v[end+1] = v[end]; end -= 1; } else break; } v[end+1] = x; } } //作用:选取[left,right]的一个数为基准值 //然后把这个区间内的元素划分为: 左边<pivot 中间=pivot 右边>pivot int* PartSort(vector<int>& v,int left,int right,int pivot) { int less = left;// <pivot的区域 [left,less) int more = right;// >pivot的区域 (more,right] int cur = left; //从left位置开始往后走,直到和more相遇,more位置的值也需要考察 while(cur <= more) { if(v[cur] > pivot) ::swap(v[more--],v[cur]); //当前数换到大于区域当中,还要继续考察换过来的这个数 else if(v[cur] < pivot) ::swap(v[less++],v[cur++]);//当前数换到小于区域当中,然后cur往后走 else cur++; } //[left,less) [less,more] (more,right] return new int[2]{less,more}; } void QuickSort(vector<int>& v,int left,int right) { if(left >= right) return ; if(right - left +1 < 10) { InsertSort_QuickSort(v,left,right); } else { //结合随机选数 ::swap(v[right],v[left + rand() %(right - left + 1)]); //PartSort以最右边的值为划分值,返回的是 等于arr[right]的区域的范围 [equalArea[0],equalArea[1]] int* equalArea = PartSort(v,left,right,v[right]); //[left,equalArea[0]-1] [equalArea[1] + 1,right] QuickSort(v,left,equalArea[0]-1); QuickSort(v,equalArea[1]+1,right); } }
递归的时候,栈帧里面存放的是左右区间的下标,我们可以把左右区间的下标值放到栈中
注意:
1.因为栈是后进先出的,所以如果先排序左区间再排序右区间,则要先把右区间的左右下标先进栈
2.循环结束的条件:栈为空。如果栈不为空,说明还有区间要进行处理
能过!
void QuickSortNonR(vector<int>& v,int left,int right) { if(left >= right) return ; stack<int> st;//存储要排序的区间 //注意:栈的特性为后进先出 st.push(right); st.push(left); while(!st.empty()) //还有区间要排序 { int begin = st.top();//左端点 st.pop(); int end = st.top();//右端点 st.pop(); if(end - begin + 1 < 10) { InsertSort_QuickSort(v,begin,end); continue; } ::swap(v[end],v[begin + rand() % (end - begin + 1)]); int* equalArea = PartSort(v,begin,end,v[end]); //[left,equalArea[0]-1] [equalArea[1] + 1,right] if(right > equalArea[1] + 1) { QuickSortNonR(v,equalArea[1]+1,right); //仍然需要递归 } if(equalArea[0]-1 > left) { QuickSortNonR(v,left,equalArea[0]-1); } } }
不能过
void Insert_Quick(vector<int>& v,int left,int right) { for(int i = left;i<right;i++) //i < right!!!! { int end = i; int x = v[end+1]; while(end >= left) //坑!! end最多移动到left位置 { if(v[end] > x) v[end + 1 ] = v[end],end--; else break; } v[end + 1] = x; } } void QuickSortNonR(vector<int>& v,int left,int right) { if(left>=right) return ; stack<int> st; st.push(left); st.push(right); while(!st.empty()) { //栈的特性:后进先出,所以先拿到右端点 int end = st.top(); st.pop(); int begin = st.top(); st.pop(); if(end - begin + 1 < 10) { Insert_Quick(v,begin,end); continue; } ::swap(v[begin],v[begin + rand() % (end - begin + 1)]);//左边作为基准值 int keyi = PartSort(v,begin,end); //[begin,keyi-1] key [keyi+1,end] if(keyi-1 > begin) { st.push(begin); st.push(keyi-1); } if(end > keyi+1) { st.push(keyi+1); st.push(end); } } }
思想:根据左右区间的值,计算一个中间值mid,先让[left,mid] [mid+1,right]两个区间有序, 然后这两个有序区间进行归并 (归并到临时数组),将临时数组的内容拷贝回去
时间复杂度:O(N*logN)
空间复杂度:O(N)
稳定性:稳定
void MergeSort(vector<int>& v,int left,int right,vector<int>& tmp) { if(left >= right) //数组只有一个元素/区间不合法就结束 return ; //求中间值:left + (right - left) / 2 或者 (left + right) >> 1 int mid = (left + right) / 2; MergeSort(v,left,mid,tmp);//左区间[left,mid]排成有序 MergeSort(v,mid+1,right,tmp);//右区间[mid+1,right]排成有序 //左右区间进行归并 int begin1 = left,end1 = mid,begin2 = mid+1,end2 = right; int index = left;//拷贝到临时数组的哪个位置 while(begin1 <= end1 && begin2 <= end2) { //排升序,谁小拷贝谁 if(v[begin1] > v[begin2]) tmp[index++] = v[begin2++]; else //相同的时候,先拷贝左边,再拷贝右边==>稳定 tmp[index++] = v[begin1++]; } //某个区间可能还未拷贝完,继续拷贝 while(begin1 <= end1) tmp[index++] = v[begin1++]; while(begin2 <= end2) tmp[index++] = v[begin2++]; //将临时数组的数据重新拷贝回去原数组,注意起始位置为left!! [left,right]区间 for(int i = left;i<=right;i++) v[i] = tmp[i]; }
非递归:1.改成循环的写法 2.用栈/队列模拟
关于gap
int gap = 1;//gap为几,就几个几个一起归并
for(int i = 0;i<n;i += 2*gap)
{
//[i,i+gap-1] [i+gap,i+2*gap-1]
}
gap = 1: [0,0][1,1]的为一组 ... 一个元素为一个区间,两个区间归并
gap = 2: [0,1][2,3]的为一组 ... 两个元素为一个区间,两个区间归并
gap = 4: [0,3][4,7]的为一组 ... 四个元素为一个区间,两个区间归并
//如何控制多组?->即控制gap ==>控制多次归并 11归并 22归并 44归并
int gap = 1;
while(gap < n)
{
gap *= 2;
}
写法1:
要修正,不能使用break的根本原因:整体归并完才拷贝,把tmp数组的内容拷贝回原数组放在了循环外面,若提前break,后面越界区间的值并没有拷贝到tmp数组中,导致tmp数组再拷贝回去的值可能覆盖有越界区间的值,把随机值拷贝回去原数组.
void MergeSortNonR(vector<int>& v) { int gap = 1,n = v.size(); vector<int> tmp(n);//辅助数组 while(gap < n) //gap为几:几个几个元素之间归并 { //推导范围:因为gap个元素作为一个区间,两个区间进行归并,所以下一轮:i+=2*gap //[i,i+gap-1] [i+gap,i + 2* gap - 1] for(int i = 0;i<n;i += 2*gap) { int begin1 = i,end1 = i+gap-1; int begin2 = i + gap,end2 = i+2*gap-1; //因为是整体归并完才拷贝 //把tmp数组的内容拷贝回原数组放在了循环外面 //若提前break,后面越界区间的值并没有拷贝到tmp数组中,导致tmp数组再拷贝回去的值可能覆盖有越界区间的值,把随机值拷贝回去原数组. if (end1 >= n) end1 = n - 1;//end1越界,[begin2,end2]不存在 if (begin2 >= n)//[begin2,end2]不存在 { //修正成不存在的区间 begin2>end2 begin2 = n ; end2 = n -1; } if (end2 >= n)//end2越界 end2 = n - 1; //[begin1,end1] [begin2,end2] int index = i;//归并元素从tmp的哪个位置开始向后拷贝 while(begin1 <= end1 && begin2 <= end2) { if(v[begin1] > v[begin2]) tmp[index++] = v[begin2++]; else tmp[index++] = v[begin1++]; } while(begin1 <= end1) tmp[index++] = v[begin1++]; while(begin2 <= end2) tmp[index++] = v[begin2++]; } //所有的11归并 22归并结束之后,再整体[0,n-1]区间拷贝回去 for (int i = 0; i < n; i++) v[i] = tmp[i]; gap *= 2; } }
写法2:归并一部分,拷贝一部分回去
对于此时:[begin1,end1] [begin2,end2]
void MergeSortNonR(vector<int>& v) { int gap = 1,n = v.size(); vector<int> tmp(n);//辅助数组 while(gap < n) //gap为几:几个几个元素之间归并 { //推导范围:因为gap个元素作为一个区间,两个区间进行归并,所以下一轮:i+=2*gap //[i,i+gap-1] [i+gap,i + 2* gap - 1] for(int i = 0;i<n;i += 2*gap) { int begin1 = i,end1 = i+gap-1; int begin2 = i + gap,end2 = i+2*gap-1; //由于是归并一部分,然后拷贝一部分回去,所以如果某一个区间越界了,那么本轮就不需要归并了 if(begin1 >=n || begin2 >= n) break; if(end2 >= n) end2 = n-1;//修正结束位置 //[begin1,end1] [begin2,end2] int index = i;//归并元素从tmp的哪个位置开始向后拷贝 while(begin1 <= end1 && begin2 <= end2) { if(v[begin1] > v[begin2]) tmp[index++] = v[begin2++]; else tmp[index++] = v[begin1++]; } while(begin1 <= end1) tmp[index++] = v[begin1++]; while(begin2 <= end2) tmp[index++] = v[begin2++]; //拷贝回去原数组 区间为:[i(begin1),end2] //由于begin1已经改变,所以要使用i变量 for(int j = i;j<=end2;j++) { v[j] = tmp[j]; } } gap *= 2; } }
主要思想:统计相同元素出现次数,根据统计的结果将序列写回到原来的序列中
把要排序数组的值放到count对应下标位置
缺点:空间消耗大.而且针对负数也不好解决
void CountSort(vector<int>& v) { int n = v.size(); //1.找出数组的最大值 int maxNum = v[0]; for(int i = 1;i<n;i++) if(v[i] > maxNum) maxNum = v[i]; //2.每个元素映射到count数组的对应位置 vector<int> count(maxNum+1,0);//注意要开maxNum+1个空间! for(auto& x:v) count[x]++; //3.将count数组的内容映射回去原数组 int index = 0; for(int i = 0;i<=maxNum;i++) //注意:i<=maxNum { while(count[i] > 0)//元素i的出现次数为count[i] { v[index++] = i; count[i]--;//i元素出现次数--,否则死循环 } } }
找出要排序数组的最大值和最小值,开max - min + 1个空间: [min,max]元素个数就是max - min + 1。将元素映射在count数组的位置是: a[i] - min
位置, 到时候放回去原数组的值是:a[i] + min
void CountSort(vector<int>& v) { int n = v.size(); //1.找出数组的最大值 int maxNum = v[0]; int minNum = v[0]; for(int i = 1;i<n;i++) { if(v[i] > maxNum) maxNum = v[i]; if(v[i] < minNum) minNum = v[i]; } int range = maxNum - minNum + 1;//数组元素范围:[minNum,maxNum] //2.数组元素进行映射。此时x元素映射在x - minNum位置 vector<int> count(range,0); for(auto& x:v) count[x - minNum]++; //3.将count数组的内容映射回去原数组,此时对应的值为i + minNum int index = 0; for(int i = 0;i< range;i++) { while(count[i] > 0)//元素i的出现次数为count[i] { v[index++] = i + minNum; count[i]--;//i元素出现次数--,否则死循环 } } }
时间复杂度:O(MAX(N,范围))
空间复杂度:O(范围)
稳定性:不稳定
横向对比:
对于已经有序的数组排序,直接插入和冒泡排序效率一样高,然而对接近有序数组,直接插入排序更好,需要的比较次数更少一点,所以后续的快速排序小区间优化使用的就是直接插入排序
注意:
1.对于快速排序:如果加了三数取中 + 三路归并 最坏就不是O(N^2)
2.为了绝对的速度选快排,为了省空间选堆排,为了稳定性选归并
3.时间复杂度:O(N*logN),额外空间复杂度低于O(N),且稳定的基于比较的排序是不存在的
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。