几大排序的稳定性

以前看人利用口诀记忆排序的稳定性:考研好痛苦,一堆(堆排序)专业课,情绪不稳定(不稳定排序),快(快速排序)来选(选择排序)一些(希尔排序)朋友聊聊天吧,剩下的都是稳定的。

感觉不错。。。。。。

接下来是八大排序总结 :

(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上的排序题,这里采用插入排序加快速排序结合:

  

下面提供数据结构与算法分析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

  

posted @   鸿钧三清  Views(1386)  Comments(0Edit  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· 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工具
点击右上角即可分享
微信分享提示