排序大体分为两类:比较排序和非比较排序
一 各个排序的基本实现
1.直接插入排序和希尔排序
//整体思路:往一段有序区间插入元素,end标记为有序区间的最后一个元素下标,要插入的元素下标为end+1此处就称tmp,先把他保存起来,(否则可能被覆盖)如果end+1这个元素 //比有序区间的小,就把有序区间元素依次向后移动,移动结束条件有两个,1.end变为-1,2.有序区间内找到比tmp小的数。 void PrintArray ( int *a, size_t n ) { for ( size_t i = 0; i < n ; i++ ) { cout << a[i] << " "; } cout << endl; } //时间复杂度:o(N^2) 最坏情况,每次插入要把前面的全移动 1+2+....n-1等差数列求和(n-1)*n/2也就是o(N^2)逆序时最坏 // o(N) 顺序最好,只比较了一遍没进去 void InsertSort ( int *a, size_t n ) { assert ( a ); for ( size_t i = 0; i < n-1; i++ ) { int end = i; //单趟逻辑 int tmp = a[end + 1]; while (end>=0&& a[end] >tmp ) { a[end + 1] = a[end]; --end; } a[end + 1] = tmp; } } //整体思路1.预排序(使大的数很快移到后面去)分组在每组内移动接近有序 gap越大越不接近有序 2.插入排序 //1.gap>1 预排序 //2.gap==1 插入排序 void ShellSort ( int*a, size_t n )//是针对插入排序逆序的情况下,移动次数太多而设计。 希尔排序用于数据量较大 { assert ( a ); int gap=n; //预排序:排完说明分组为gap的这些元素各自有序 while ( gap > 1 ) { gap = gap / 3+1;//加1保证了最后一次为gap=1,绝对会有序 for ( size_t i = 0; i<n - gap; i++ )//i++保证了不是一组走完,走另一组。而是一起走。 { int end = i; //单趟排序 int tmp = a[end + gap]; while ( end >= 0 && a[end] > tmp ) { a[end+gap] = a[end ]; end -= gap; } a[end + gap] = tmp; } } } void TestInsertSort ( ) { int a[] = { 2, 5, 4, 9, 3, 6, 8, 7, 1 }; //InsertSort ( a, sizeof(a) / sizeof(a[0])); PrintArray ( a, sizeof(a) / sizeof(a[0]) ); } void TestShellSort ( ) { int a[] = { 2, 5, 4, 9, 3, 6, 8, 7, 1 }; ShellSort ( a, sizeof(a) / sizeof(a[0])); PrintArray ( a, sizeof(a) / sizeof(a[0]) ); }
2.选择排序和堆排序
//选择排序 每次可选一个最小的数,一个最大的数 //时间复杂度 最坏o(N^2) n-1+n-2+1 也是等差数列 //时间复杂度 最好o(N^2) 尽管你有序,可是我不知道还是要每次遍历一遍 void SelectSort ( int *a, size_t n ) { int end = n-1; int begin= 0; while ( begin< end ) { int min = begin; int max = begin; for ( size_t i = begin; i <= end; i++ ) { if ( a[i]>a[max] ) { max = i; } if ( a[i] < a[min ]) { min = i; } } /* swap ( a[min], a[begin] ); if (begin== max ) { max = min; } swap ( a[max], a[end] );*/ swap ( a[max], a[end] ); if ( min == end ) { min = max; } swap ( a[min], a[begin] ); begin++; end--; } } //堆排序 升序 建大堆,把最大的数选出来,换到后面去,然后把剩下的数向下调整看成一个堆 //选第一个数要建堆N*lg N 其他lgn 即N*lgN+(N-1)lgN=o(NlgN) void AdjustDown ( int *a, size_t n, int root ) { int parent = root; int child = 2 * parent; while ( child<n )//如果孩子都不存在说名到叶节点,就停止向下调整 { if ( child+1<n && a[child + 1] > a[child] ) { child++; } if ( a[child] > a[parent] ) { swap ( a[child], a[parent] ); parent = child; child = parent * 2; } else { break; } } } void HeapSort ( int *a, size_t n ) { for ( int i = (n - 2) / 2; i >= 0; i-- )//建堆NlgN { AdjustDown ( a, n, i );//lgN } //(N-1)lgN int end = n - 1; while ( end > 0 ) { swap ( a[0], a[end] ); AdjustDown ( a, end, 0 ); --end; } } void TestHeapSort ( ) { int a[] = { 2, 5, 4, 9, 3, 6, 8, 7, 1, 0 }; HeapSort ( a, sizeof(a) / sizeof(a[0]) ); PrintArray ( a, sizeof(a) / sizeof(a[0]) ); } void TestSelectSort ( ) { int a[] = { 2, 5, 4, 9, 3, 6, 8, 7, 1 ,0}; SelectSort ( a, sizeof(a) / sizeof(a[0])); PrintArray ( a, sizeof(a) / sizeof(a[0]) ); } int main ( ) { //TestSelectSort ( ); TestHeapSort ( ); system ( "pause" ); return 0; }
3.冒泡排序和快速排序
冒泡排序
//交换排序 //时间复杂度0(N^2) n-1+n-2+.....1 也是等差数列 //最好的情况下:0(N) 有序 //冒泡和插入的区别:插入比冒泡好,冒泡要求更严格的有序 //比如:123465 如果插入排序 是比较N-1次,插入一次 N 冒泡排序:第一趟比较 N-1次之后,已经有序可是不知道,又要来一遍 N-1 //因此插入比冒泡好,冒泡要求更严格有序 void BubbleSort ( int *a, size_t n ) { //int end = n - 1; //while ( end > 0 ) //{ // bool ExChange = 0; // for ( size_t i = 0; i < end; i++ )//单趟 // { // if ( a[i]>a[i + 1] ) // { // swap ( a[i], a[i + 1] ); // ExChange = 1; // } // } // if ( ExChange == 0 ) // { // break; // } // end--; //} for ( size_t end = n - 1; end > 0; end-- ) { bool ExChange = 0; for ( size_t i = 0; i < end; i++ )//单趟 { if ( a[i]>a[i + 1] ) { swap ( a[i], a[i + 1] ); ExChange = 1; } } if ( ExChange == 0 ) { break; } } } void TestBubbleSort ( ) { int a[] = { 2, 5, 4, 9, 3, 6, 8, 7, 1, 0 }; BubbleSort ( a, sizeof(a) / sizeof(a[0]) ); PrintArray ( a, sizeof(a) / sizeof(a[0]) ); }
快速排序:
//时间复杂度:递归的次数乘以每次递归 递归的次数N每次递归lgN 因此时间复杂度为o(NlgN) //最坏情况:0(N^2) 有序 三数取中法,解决了有序的这种情况 int GetMidindex ( int *a, int begin, int end ) { int mid = begin + ((end - begin) >> 1); if ( a[mid] > a[begin] ) { if ( a[begin] > a[end] ) { return begin; } else if ( a[mid] > a[end] ) { return end; } else { return mid; } } else { if ( a[begin] > a[end] ) { return begin; } else if ( a[end] > a[mid] ) { return mid; } else { return end; } } } //左右指针法 int PartSort2 ( int *a, int begin, int end ) { //int& key= a[end];//为什莫给a[end]而不是和讲的一样是a[end-1],注意考虑有序情况:如果给a[end-1]反而错了,如果给a[end]就自己和自己交换 int mid = GetMidindex ( a, begin, end ); swap ( a[mid], a[end] ); int keyIndex = end; int key = a[end]; while ( begin < end ) { while ( begin<end&&a[begin] <= key ) { begin++; } while (begin<end&& a[end]>=key ) { end--; } swap ( a[end], a[begin] ); } swap(a[keyIndex], a[begin]); return begin; } //挖坑法 int PartSort1 ( int*a, int begin, int end ) { int key = a[end]; while ( begin < end ) { while (begin<end&& a[begin] <=key ) { begin++; } a[end] = a[begin]; while (begin<end&& a[end] >= key ) { end--; } a[begin] = a[end]; } a[begin] = key; return begin; } //前后指针法 int PartSort3 ( int *a, int begin, int end ) { int& key = a[end]; //int key=a[end] int cur = begin; int prev = begin - 1; while ( cur < end ) { if ( a[cur] < key && (++prev) != cur ) { swap ( a[prev], a[cur] ); } cur++; } swap ( a[++prev], key );//swap(a[++prev],a[end]); return prev; } void QuicksortNonR ( int *a, int left, int right ) { stack<int >st; st.push ( right ); st.push ( left ); while ( !st.empty ( ) ) { int begin = st.top ( ); st.pop ( ); int end = st.top ( ); st.pop ( ); int div = PartSort3 ( a, begin, end ); if ( begin < div - 1 ) { st.push ( div - 1 ); st.push ( begin ); } if ( div + 1 < end ) { st.push ( end ); st.push ( div + 1 ); } } } void Quicksort ( int* a, int left,int right ) { if ( left >= right ) { return; } //小区间优化 if ( right - left < 8)//省去最后3层 { InsertSort ( a+left, (right - left) + 1 ); return; } int div = PartSort3 ( a, left, right ); Quicksort ( a, left, div - 1 ); Quicksort ( a, div + 1, right ); } //有序有两种情况:1.区间只剩一个值,说明有序 // 2.左边有序,右边有序 void TestQuickSort ( ) { int a[] = { 5, 10, 4, 9, 20, 6, 8, 7, 1, 5 }; QuicksortNonR ( a, 0, sizeof(a) / sizeof(a[0]) - 1 ); PrintArray ( a, sizeof(a) / sizeof(a[0]) ); } //int main ( ) //{ // //TestBubbleSort ( ); // // TestQuickSort ( ); // system ( "pause" ); // return 0; //}
4.归并排序
归并排序 时间复杂度:0(NlgN) 空间复杂度:0(N) void _MergeSort ( int *a, int left, int right, int* tmp )//tmp为什莫再外面开tmp,所有递归都可以用.如果在里面开里面每次递归都要开辟空间 { if ( left >= right )//如果只剩一个元素,或者没有元素可以看作是有序的 { return; } if ( right - left < 8 )//省去最后3层 { InsertSort ( a + left, (right - left) + 1 ); return; } int div = ((right - left) >> 1) + left; //让两段子区间有序再归并 //[left,div] [div+1 ,right] _MergeSort ( a, left, div, tmp ); _MergeSort ( a, div + 1, right, tmp ); int index = left; int begin1 = left; int end1 = div; int begin2 = div + 1; int end2 = right; while ((begin1<=end1)&&(begin2<=end2)) { if ( a[begin1] <= a[begin2] ) { tmp[index++] = a[begin1++]; } else { tmp[index++] = a[begin2++]; } } while( begin1 <= end1 ) { tmp[index++] = a[begin1++]; } while (begin2<=end2 ) { tmp[index++] = a[begin2++]; } //每次归并完,再拷贝到原区间上去 index = left; while ( index <= right ) { a[index] = tmp[index]; ++index; } } void MergeSort ( int *a,size_t n) { int * tmp = new int[n]; _MergeSort(a, 0, n - 1,tmp); delete[]tmp; } void TestMergeSort ( ) { int a[] = { 5, 10, 4, 9, 20, 6, 8, 7, 1, 5 }; MergeSort( a, sizeof(a) / sizeof(a[0])); PrintArray ( a, sizeof(a) / sizeof(a[0]) ); } int main ( ) { TestMergeSort ( ); system ( "pause" ); return 0; }
5.计数排序
//非比较排序 //基数排序:只能用于排整型 不多讲 //计数排序: //直接定址法的哈希 //时间复杂度 0(max(n,range)) 数据范围比较集中的时候适合用计数排序 void CountSort ( int *a, int n ) { int max = a[0]; int min = a[0]; for ( int i = 0; i < n; i++ ) { if ( a[i]>max ) { max = a[i]; } if ( a[i] < min ) { min = a[i]; } } int range = max - min + 1; int * hashtable = new int[range];//不能开n,是相对位置 memset ( hashtable, 0, sizeof(int)*range ); for ( size_t i = 0; i < n; i++ ) { hashtable[a[i] - min]++;// a[i]是绝对位置,记清楚此处是相对位置 } size_t j = 0; for ( size_t i = 0; i < range; i++ ) { while ( hashtable[i]-- ) { a[j] = i + min; ++j; } } delete[] hashtable; } void TestCountSort ( ) { int a[] = { 5, 10, 4, 9, 20, 6, 8, 7, 1, 5 }; CountSort( a, sizeof(a) / sizeof(a[0])); PrintArray ( a, sizeof(a) / sizeof(a[0]) ); } int main ( ) { TestCountSort ( ); system ( "pause" ); return 0; }
二 时间复杂度和空间复杂度
三 稳定性
稳定性:应用场景:成绩排名:成绩相同,先交卷子在前,后交卷子在后
具体操作:先拿时间排,再拿一个稳定的的排序对成绩排,就能保证
各个排序的稳定性
各个排序的稳定性
首先要明白所有的稳定排序都可以变成不稳定的。
插入 稳定:我能做到比你小让你往后挪,和你相等放你后面如此便可以保证有序
希尔 不稳定:相同的值可能被分到不同的组里面 把相对顺序打乱
选择排序 不稳定 :先选到的放到最后面取
堆排序 不稳定:父亲大于等于孩子 把父亲的放到后面了,孩子的放次后面 相对位置变了
冒泡 稳定 :大的往后冒泡,相等不往后冒泡
快排 不稳定:比它大的往右翻,比它小的往左翻 最后后面的那个到中间 。 // 1 9 5 7 6 4 5 8 5
归并排序 稳定:如果相等时先拿左边的
插入 稳定:我能做到比你小让你往后挪,和你相等放你后面如此便可以保证有序
希尔 不稳定:相同的值可能被分到不同的组里面 把相对顺序打乱
选择排序 不稳定 :先选到的放到最后面取
堆排序 不稳定:父亲大于等于孩子 把父亲的放到后面了,孩子的放次后面 相对位置变了
冒泡 稳定 :大的往后冒泡,相等不往后冒泡
快排 不稳定:比它大的往右翻,比它小的往左翻 最后后面的那个到中间 。 // 1 9 5 7 6 4 5 8 5
归并排序 稳定:如果相等时先拿左边的