几大排序的稳定性
以前看人利用口诀记忆排序的稳定性:考研好痛苦,一堆(堆排序)专业课,情绪不稳定(不稳定排序),快(快速排序)来选(选择排序)一些(希尔排序)朋友聊聊天吧,剩下的都是稳定的。
感觉不错。。。。。。
接下来是八大排序总结 :
(1)冒泡排序
冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,我想你是不会再无聊地把他们俩交换一下的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。
1 void bubbleSort(int a[], int low, int high){//[low,high) 2 while(low < (high = bubble(a, low, high)));//逐趟扫描,直至全部有序 3 } 4 5 6 7 int bubble(int a[], int low, int high){ 8 int last = low; 9 while(++low<high) 10 if(a[low-1]>a[low]){ 11 swap(a[low-1],a[low]); 12 last = low;//找到上次最后一次发生交换的位置 13 } 14 return last; 15 }//平均复杂度太高了,不能用不能用。。。。。。
(2)选择排序
选择排序是给每个位置选择当前元素最小的,序列5 8 5 2 9,一轮下来变成2 8 5 5 9 。所以选择排序不是一个稳定的排序算法。
1 void select_sort(int arr[], int left, int right) { 2 for (int i = left; i < right; ++i) { 3 int min_index = i; 4 for (int j = i; j < right; ++j) { 5 if (arr[j] <= arr[min_index]) min_index = j; 6 } 7 std::swap(arr[min_index], arr[i]); 8 } 9 } // o(n^2) 复杂度较高不好用
(3)插入排序
插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。这个排序也可以利用STL的sort来模拟,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。这个排序在元素个数较少或者序列基本有序的情况下速度是非常可观的,对一组序列如果数据量很大的时候可以主要用快排,当快排将区间划分的较小的时候切换排序方式为插入排序(如果快速排序递归栈过深也可采用堆排序避免)。
1 void insertSort(int arr[], int size){ 2 for (int i = 1; i < size; i++){ 3 if(arr[i] < arr[i - 1]){ 4 int temp = arr[i]; 5 int j; 6 for(j = i - 1; j >=0 && temp < arr[j]; --j){ 7 arr[j + 1] = arr[j]; 8 } 9 arr[j + 1] = temp; 10 } 11 } 12 }//元素基本有序的情况下,这个排序的复杂度很低,挺实用的,附上代码一份
(4)快速排序
简单来说就是选取一个中枢元素,一轮下来比中枢元素的小的在一边,比中枢元素大的在另外一边,所以每一轮可以将这一轮的中枢元素移动到其最终位置上(用此方法可以快速找到数组中第K个大小的元素而无需使用堆这一数据结构),这个
比如序列为5 6 3 4 3,以5为中枢元素,一轮下来该序列变成3 4 3 5 6,(5已经在最终位置上,但是两个3的相对位置发生了改变)。
1 void Qsort2(int arr[], int low, int high){ 2 if(low >= high) return; 3 int i = low; 4 int j = high; 5 int key = arr[low]; 6 while(i < j){ 7 while(i < j && key <= arr[j]) j--; 8 arr[i] = arr[j]; 9 while(i < j && key >= arr[i]) i++; 10 arr[j] = arr[i]; 11 } 12 arr[i] = key; 13 Qsort2(arr, low, i - 1); 14 Qsort2(arr, i + 1, high); 15 }//复杂度o(nlogn),速度很快但是递归过程中可能爆栈,总的来说还是非常实用的
1 void Qsort(int arr[], int l, int r) { 2 if (l >= r) return; 3 4 int i = l - 1; 5 int j = r + 1; 6 int x = arr[i + j >> 1]; 7 while (i < j) { 8 while (arr[++i] < x); 9 while (arr[--j] > x); 10 if (i < j) swap(arr[i], arr[j]); 11 } 12 Qsort(arr, l, j); 13 Qsort(arr, j + 1, r); 14 }
(5)归并排序
归并排序是针对两个已经排好序的序列,将这;两个序列合并成一个有序序列的排序方法,对于一个长度为n未排序的序列,我们一开始可以假设它是n个长度为1的有序序列构成,然后进行一次合并变成 n / 2 个长度为2的有序序列,然后再一次合并成
n / 4个长度为4的有序序列,以此类推。。。进行log2 (n)次后整个未排序序列就会变成一个长度为n的有序序列,时间复杂度为O(logn)
归并排序是稳定的排序算法。
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 ListNode* sortList(ListNode* head) { 12 int n{}; 13 for (auto p = head; p; p = p->next) ++n; 14 auto dummy = new ListNode(0); 15 dummy->next = head; 16 // i是每轮区间的长度 17 for (int i = 1; i < n; i <<= 1) { 18 auto cur = dummy; 19 // j是每轮排序的两个区间的第一个节点 20 for (int j = 0; j + i < n; j += i << 1) { 21 // left是左边起点, right是右边起点 22 auto left = cur->next, right = cur->next; 23 for (int k = 0; k < i; ++k) right = right->next; 24 // l是左边以及排好序的节点数,r是右边已经排好序的节点数 25 int l = 0, r = 0; 26 while (l < i && r < i && right) { 27 if (left->val < right->val) { 28 cur->next = left; 29 cur = left; 30 left = left->next; 31 l++; 32 } 33 else { 34 cur->next = right; 35 cur = right; 36 right = right->next; 37 r++; 38 } 39 } 40 while (l < i) { 41 cur->next = left; 42 cur = left; 43 left = left->next; 44 l++; 45 } 46 while (r < i && right) { 47 cur->next = right; 48 cur = right; 49 right = right->next; 50 r++; 51 } 52 cur->next = right; 53 } 54 } 55 56 return dummy->next; 57 } 58 }; // leetcode 148 链表的归并排序, 时间复杂度O(n), 空间复杂度O(1)
(6)基数排序 (桶排序)
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序,最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以其是稳定的排序算法。
(7)希尔排序(shell)
希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小, 插入排序对于有序的序列效率很高。所以,希尔排序的时间复杂度会比O(n^2)好一些。由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。
听说复杂度可以到o(n^1.3),没怎么见过人使用。
(8)堆排序
我们知道堆的结构是节点i的孩子为2 * i和2 * i + 1节点,大顶堆要求父节点大于等于其2个子节点,小顶堆要求父节点小于等于其2个子节点。在一个长为n 的序列,堆排序的过程是从第n / 2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n / 2 - 1, n / 2 - 2, ... 1这些个父节点选择元素时,就会破坏稳定性。有可能第n / 2个父节点交换把后面一个元素交换过去了,而第n / 2 - 1个父节点把后面一个相同的元素没 有交换,那么这2个相同的元素之间的稳定性就被破坏了。所以,堆排序不是稳定的排序算法。
可以用STL中make_heap来建堆并进行模拟,在复杂度和快速排序一样是o(nlogn)但是听说在排序过程中有较多的 << 1 和 >> 1操作,对CPU来说并不是非常友好,而快速排序都是++或者--操作,
所以STL的sort只有在递归栈过深的情况下才会使用堆排序,代码参见数据结构——堆的代码即可。
3 2 2 --------------------------------->(after sort)------------------------>2 2 3 (两个2的相对位置变了)
3 2
/ \ / \
2 2 2 3
// 0
// 1 2
// 3 4 5 6 ==> left_child = index * 2 + 1
inline int left_child(int index) { return index << 1 | 1; } void adjust_down(int arr[], int index, int size) { int child, temp; for (temp = arr[index]; left_child(index) < size; index = child) { child = left_child(index); // left child if (child != size - 1 && arr[child] < arr[child + 1]) { // has right child and left child // less than right child ++child; // make child point greater node } if (temp < arr[child]) { arr[index] = arr[child]; } else break; } arr[index] = temp; } // Max heap void heap_sort(int arr[], int size) { // make heap for (int i = (size >> 1) - 1; i >= 0; --i) { adjust_down(arr, i, size); } for (int i = size - 1; i > 0; --i) { int max = arr[0]; arr[0] = arr[i]; arr[i] = max; // swap(arr[0], arr[last]) adjust_down(arr, 0, i); } }
综上,得出结论: 选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法,而冒泡排序、插入排序、归并排序和基数排序是稳定的排序算法
一道LeetCode上的排序题,这里采用插入排序加快速排序结合:
class Solution { public : //获取枢纽元素,这里不取第一个元素为枢纽元素,要避免这种愚蠢的做法 int median(vector< int > &arr, int left, int right) { int center = left + right >> 1; if (arr[center] < arr[left]) swap(arr[center], arr[left]); if (arr[right] < arr[left]) swap(arr[left], arr[right]); if (arr[right] < arr[center]) swap(arr[right], arr[center]); swap(arr[center], arr[right - 1]); return arr[right - 1]; } void quickSort(vector< int > &arr, int left, int right) { if (left + 10 <= right) { int pivot = median(arr, left, right); int i = left, j = right - 1; while (1) { while (arr[++i] < pivot); while (arr[--j] > pivot); if (i < j) swap(arr[i], arr[j]); else break ; } swap(arr[i], arr[right - 1]); quickSort(arr, left, i - 1); quickSort(arr, i + 1, right); } else insertionSort(arr, left, right); } void insertionSort(vector< int > &arr, int left, int right) { for ( int i = left + 1; i <= right; ++i) { auto tmp = arr[i]; int j; for (j = i; j > 0 && tmp < arr[j - 1]; --j) { arr[j] = arr[j - 1]; } arr[j] = tmp; } } vector< int > sortArray(vector< int >& nums) { quickSort(nums, 0, nums.size() - 1); return {nums}; } }; |
下面提供数据结构与算法分析C++版中的几大排序的源码
网址:http://users.cis.fiu.edu/~weiss/dsaa_c++4/code/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 | #ifndef SORT_H #define SORT_H /** * Several sorting routines. * Arrays are rearranged with smallest item first. */ #include <vector> #include <functional> using namespace std; /** * Simple insertion sort. */ template < typename Comparable> void insertionSort( vector<Comparable> & a ) { for ( int p = 1; p < a.size( ); ++p ) { Comparable tmp = std::move( a[ p ] ); int j; for ( j = p; j > 0 && tmp < a[ j - 1 ]; --j ) a[ j ] = std::move( a[ j - 1 ] ); a[ j ] = std::move( tmp ); } } /** * Internal insertion sort routine for subarrays * that is used by quicksort. * a is an array of Comparable items. * left is the left-most index of the subarray. * right is the right-most index of the subarray. */ template < typename Comparable> void insertionSort( vector<Comparable> & a, int left, int right ) { for ( int p = left + 1; p <= right; ++p ) { Comparable tmp = std::move( a[ p ] ); int j; for ( j = p; j > left && tmp < a[ j - 1 ]; --j ) a[ j ] = std::move( a[ j - 1 ] ); a[ j ] = std::move( tmp ); } } /** * Shellsort, using Shell's (poor) increments. */ template < typename Comparable> void shellsort( vector<Comparable> & a ) { for ( int gap = a.size( ) / 2; gap > 0; gap /= 2 ) for ( int i = gap; i < a.size( ); ++i ) { Comparable tmp = std::move( a[ i ] ); int j = i; for ( ; j >= gap && tmp < a[ j - gap ]; j -= gap ) a[ j ] = std::move( a[ j - gap ] ); a[ j ] = std::move( tmp ); } } /** * Standard heapsort. */ template < typename Comparable> void heapsort( vector<Comparable> & a ) { for ( int i = a.size( ) / 2 - 1; i >= 0; --i ) /* buildHeap */ percDown( a, i, a.size( ) ); for ( int j = a.size( ) - 1; j > 0; --j ) { std::swap( a[ 0 ], a[ j ] ); /* deleteMax */ percDown( a, 0, j ); } } /** * Internal method for heapsort. * i is the index of an item in the heap. * Returns the index of the left child. */ inline int leftChild( int i ) { return 2 * i + 1; } /** * Internal method for heapsort that is used in * deleteMax and buildHeap. * i is the position from which to percolate down. * n is the logical size of the binary heap. */ template < typename Comparable> void percDown( vector<Comparable> & a, int i, int n ) { int child; Comparable tmp; for ( tmp = std::move( a[ i ] ); leftChild( i ) < n; i = child ) { child = leftChild( i ); if ( child != n - 1 && a[ child ] < a[ child + 1 ] ) ++child; if ( tmp < a[ child ] ) a[ i ] = std::move( a[ child ] ); else break ; } a[ i ] = std::move( tmp ); } /** * Internal method that makes recursive calls. * a is an array of Comparable items. * tmpArray is an array to place the merged result. * left is the left-most index of the subarray. * right is the right-most index of the subarray. */ template < typename Comparable> void mergeSort( vector<Comparable> & a, vector<Comparable> & tmpArray, int left, int right ) { if ( left < right ) { int center = ( left + right ) / 2; mergeSort( a, tmpArray, left, center ); mergeSort( a, tmpArray, center + 1, right ); merge( a, tmpArray, left, center + 1, right ); } } /** * Mergesort algorithm (driver). */ template < typename Comparable> void mergeSort( vector<Comparable> & a ) { vector<Comparable> tmpArray( a.size( ) ); mergeSort( a, tmpArray, 0, a.size( ) - 1 ); } /** * Internal method that merges two sorted halves of a subarray. * a is an array of Comparable items. * tmpArray is an array to place the merged result. * leftPos is the left-most index of the subarray. * rightPos is the index of the start of the second half. * rightEnd is the right-most index of the subarray. */ template < typename Comparable> void merge( vector<Comparable> & a, vector<Comparable> & tmpArray, int leftPos, int rightPos, int rightEnd ) { int leftEnd = rightPos - 1; int tmpPos = leftPos; int numElements = rightEnd - leftPos + 1; // Main loop while ( leftPos <= leftEnd && rightPos <= rightEnd ) if ( a[ leftPos ] <= a[ rightPos ] ) tmpArray[ tmpPos++ ] = std::move( a[ leftPos++ ] ); else tmpArray[ tmpPos++ ] = std::move( a[ rightPos++ ] ); while ( leftPos <= leftEnd ) // Copy rest of first half tmpArray[ tmpPos++ ] = std::move( a[ leftPos++ ] ); while ( rightPos <= rightEnd ) // Copy rest of right half tmpArray[ tmpPos++ ] = std::move( a[ rightPos++ ] ); // Copy tmpArray back for ( int i = 0; i < numElements; ++i, --rightEnd ) a[ rightEnd ] = std::move( tmpArray[ rightEnd ] ); } /** * Return median of left, center, and right. * Order these and hide the pivot. */ template < typename Comparable> const Comparable & median3( vector<Comparable> & a, int left, int right ) { int center = ( left + right ) / 2; if ( a[ center ] < a[ left ] ) std::swap( a[ left ], a[ center ] ); if ( a[ right ] < a[ left ] ) std::swap( a[ left ], a[ right ] ); if ( a[ right ] < a[ center ] ) std::swap( a[ center ], a[ right ] ); // Place pivot at position right - 1 std::swap( a[ center ], a[ right - 1 ] ); return a[ right - 1 ]; } /** * Internal quicksort method that makes recursive calls. * Uses median-of-three partitioning and a cutoff of 10. * a is an array of Comparable items. * left is the left-most index of the subarray. * right is the right-most index of the subarray. */ template < typename Comparable> void quicksort( vector<Comparable> & a, int left, int right ) { if ( left + 10 <= right ) { const Comparable & pivot = median3( a, left, right ); // Begin partitioning int i = left, j = right - 1; for ( ; ; ) { while ( a[ ++i ] < pivot ) { } while ( pivot < a[ --j ] ) { } if ( i < j ) std::swap( a[ i ], a[ j ] ); else break ; } std::swap( a[ i ], a[ right - 1 ] ); // Restore pivot quicksort( a, left, i - 1 ); // Sort small elements quicksort( a, i + 1, right ); // Sort large elements } else // Do an insertion sort on the subarray insertionSort( a, left, right ); } /** * Quicksort algorithm (driver). */ template < typename Comparable> void quicksort( vector<Comparable> & a ) { quicksort( a, 0, a.size( ) - 1 ); } /** * Internal selection method that makes recursive calls. * Uses median-of-three partitioning and a cutoff of 10. * Places the kth smallest item in a[k-1]. * a is an array of Comparable items. * left is the left-most index of the subarray. * right is the right-most index of the subarray. * k is the desired rank (1 is minimum) in the entire array. */ template < typename Comparable> void quickSelect( vector<Comparable> & a, int left, int right, int k ) { if ( left + 10 <= right ) { const Comparable & pivot = median3( a, left, right ); // Begin partitioning int i = left, j = right - 1; for ( ; ; ) { while ( a[ ++i ] < pivot ) { } while ( pivot < a[ --j ] ) { } if ( i < j ) std::swap( a[ i ], a[ j ] ); else break ; } std::swap( a[ i ], a[ right - 1 ] ); // Restore pivot // Recurse; only this part changes if ( k <= i ) quickSelect( a, left, i - 1, k ); else if ( k > i + 1 ) quickSelect( a, i + 1, right, k ); } else // Do an insertion sort on the subarray insertionSort( a, left, right ); } /** * Quick selection algorithm. * Places the kth smallest item in a[k-1]. * a is an array of Comparable items. * k is the desired rank (1 is minimum) in the entire array. */ template < typename Comparable> void quickSelect( vector<Comparable> & a, int k ) { quickSelect( a, 0, a.size( ) - 1, k ); } template < typename Comparable> void SORT( vector<Comparable> & items ) { if ( items.size( ) > 1 ) { vector<Comparable> smaller; vector<Comparable> same; vector<Comparable> larger; auto chosenItem = items[ items.size( ) / 2 ]; for ( auto & i : items ) { if ( i < chosenItem ) smaller.push_back( std::move( i ) ); else if ( chosenItem < i ) larger.push_back( std::move( i ) ); else same.push_back( std::move( i ) ); } SORT( smaller ); // Recursive call! SORT( larger ); // Recursive call! std::move( begin( smaller ), end( smaller ), begin( items ) ); std::move( begin( same ), end( same ), begin( items ) + smaller.size( ) ); std::move( begin( larger ), end( larger ), end( items ) - larger.size( ) ); /* items.clear( ); items.insert( end( items ), begin( smaller ), end( smaller ) ); items.insert( end( items ), begin( same ), end( same ) ); items.insert( end( items ), begin( larger ), end( larger ) ); */ } } /* * This is the more public version of insertion sort. * It requires a pair of iterators and a comparison * function object. */ template < typename RandomIterator, typename Comparator> void insertionSort( const RandomIterator & begin, const RandomIterator & end, Comparator lessThan ) { if ( begin == end ) return ; RandomIterator j; for ( RandomIterator p = begin+1; p != end; ++p ) { auto tmp = std::move( *p ); for ( j = p; j != begin && lessThan( tmp, *( j-1 ) ); --j ) *j = std::move( *(j-1) ); *j = std::move( tmp ); } } /* * The two-parameter version calls the three parameter version, using C++11 decltype */ template < typename RandomIterator> void insertionSort( const RandomIterator & begin, const RandomIterator & end ) { insertionSort( begin, end, less< decltype (*begin )>{ } ); } #endif |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具