1.快速排序
在学习数据结构时我已经讲过快速排序了,就是用hoare的单多趟实现完成的,今天我们用算法玩一玩。
给你N个数让你升序排列区间[l,r]的每一个数。
上次用hoare法写的时候是双指针,一个是begin从开始,一个end从末尾开始,给一个标准值 。begin收集比标准值小,end收集大的,找到了就交换。
//快速排序单趟实现 int PartSort1(int* a, int begin, int end) { int left = begin; int right = end; int key = begin; int keyi = begin; if (begin >= end)//如果最多有一个就不用递归了 { return; } while (left < right) { while (left < right && a[right] >= a[key]) { right--; } while (left < right && a[left] <= a[key]) { left++; } Swap(&a[left], &a[right]); } Swap(&a[left], &a[keyi]); return left;
我觉得这个写法有点邋遢,换个新玩法。
快速实现:
我们要排序[l, r]上的数字,我们直接从[l-1,r+1]开始,直接先+1,这样就不用在考虑边界的情况了。
#include<iostream> using namespace std; const int N = 1e6 + 10; int n; int q[N]; void quick_sort(int q[], int l, int r) { if (l >= r) return; int i = l - 1, j = r + 1, x = q[l+r>>1]; //q[l]也行 while (i < j) { do i++; while (q[i] < x); //找到比标准值大的就跳出循环 do j--; while (q[j] > x);//找到比标准值小的的就跳出循环 if (i < j) swap(q[i], q[j]); //交换这两个另类 } quick_sort(q, l, j); quick_sort(q, j + 1, r); } int main() { while (cin >> n) { for (int i = 0; i < n; i++) { cin >> q[i]; } quick_sort(q, 0, n - 1); for (int i = 0; i < n; i++) { cout << q[i] << ' '; } } return 0; }
上面的快速排序函数就是一个模板,如果用到快排,直接套用这个模板就轻而易举的搞定了。还等什么,直接背起来。
我们来解释一下这个代码:
x:基准值:一般选择区间中间的那个数,比如区间6~10,他的基准值就是8=10000(二进制)>>1=1000。基准值也可以是q[l].无所谓。
我们从基准值分开,左边比它小,右边比他大。i是找左边比基准值大的数,j找右边比它小的数,找到就交换。
do while:就是让i此时指向的是比基准值小的数,循环继续,当它指向的是比基准值大的数时就停止,跳出循环。同理j也是这样。
两个递归作用;我们此时只是让基准值的左边小,右边大,但是左边和右边都是无序的,所以我们用递归让左边的数继续重复上面的操作来排序,一直分割区间,直到有序,跳出递归。
其实两个形参也可以:
#include<iostream> using namespace std; const int N = 1e6 + 10; int n; int q[N]; void quick_sort(int l, int r) { if (l >= r) return; int i = l - 1, j = r + 1, x = q[l]; while (i < j) { do i++; while (q[i] < x); //找到比标准值大的就跳出循环 do j--; while (q[j] > x);//找到比标准值小的的就跳出循环 if (i < j) swap(q[i], q[j]); //交换这两个另类 } quick_sort(l, j); quick_sort( j + 1, r); } int main() { while (cin >> n) { for (int i = 0; i < n; i++) { cin >> q[i]; } quick_sort( 0, n - 1); for (int i = 0; i < n; i++) { cout << q[i] << ' '; } } return 0; }
2 .归并排序
在归并排序中我们需要做三步:
- 在排序区间找中点出的下标,将区间分两半。(快排是找个数不是下标)
- 递归排序左右的区间(left,right)。
- 创建一个新数组,将上面的左右区间合二为一。
#include <iostream> using namespace std; const int N = 1e5 + 10; int a[N], tmp[N]; void merge_sort(int q[], int l, int r) { if (l >= r) return; int mid = l + r >> 1; merge_sort(q, l, mid), merge_sort(q, mid + 1, r); int k = 0, i = l, j = mid + 1; while (i <= mid && j <= r) { if (q[i] <= q[j]) tmp[k++] = q[i++]; //把较小的q[i]收入其中 else tmp[k++] = q[j++]; } while (i <= mid ) //如果j已经合并完但是i还有剩余,就让i入数组。 { tmp[k++] = q[i++]; } while (j <= r) { tmp[k++] = q[j++]; } for (i = l, j = 0; i <= r; i++, j++) //将排序好的区间加入的原来的数中 { q[i] = tmp[j]; } } int main() { int n; cin >> n; for (int i = 0; i < n; i++) { cin>>a[i]; } merge_sort(a, 0, n - 1); for (int i = 0; i < n; i++) { cout<<a[i]<<" "; } return 0; }
这个思想时先调用递归进行切割区间,把一个大区间切割成若干个小区间,然后比较小区间中的数,比较完返回更大一级区间在比较直到把left,right两个区间全部变成升序的。
让后用一个空数组来装这些数,如果其中一个全部进入另一个还有剩余,那就让剩余的直接归并到数组中。然后我们把这个升序的新数组付给区间[l,r]。要是不赋给它,那在N个数中让我们排序的区间没变。
其实归并函数两个参数也可以,把q[N]设成全局变量。
#include <iostream> using namespace std; const int N = 1e5 + 10; int q[N], tmp[N]; void merge_sort( int l, int r) { if (l >= r) return; int mid = l + r >> 1; merge_sort( l, mid), merge_sort( mid + 1, r); int k = 0, i = l, j = mid + 1; while (i <= mid && j <= r) { if (q[i] <= q[j]) tmp[k++] = q[i++]; //把较小的q[i]收入其中 else tmp[k++] = q[j++]; } while (i <= mid ) //如果j已经合并完但是i还有剩余,就让i入数组。 { tmp[k++] = q[i++]; } while (j <= r) { tmp[k++] = q[j++]; } for (i = l, j = 0; i <= r; i++, j++) //将排序好的区间加入的原来的数中 { q[i] = tmp[j]; } } int main() { int n; cin >> n; for (int i = 0; i < n; i++) { cin>>q[i]; } merge_sort( 0, n - 1); for (int i = 0; i < n; i++) { cout<<q[i]<<" "; } return 0; }
接下来我看到有位大佬总结了其余5中种排序的模板,我给各位看一下。
- 1.插入排序
void insert_sort() { for (int i = 1; i < n; i ++ ) { int x = a[i]; int j = i-1; while (j >= 0 && x < a[j]) { a[j+1] = a[j]; j -- ; } a[j+1] = x; } }
- 2.选择排序:
void select_sort() { for (int i = 0; i < n; i ++ ) { int k = i; for (int j = i+1; j < n; j ++ ) { if (a[j] < a[k]) k = j; } swap(a[i], a[k]); } }
- 3.冒泡排序:
void bubble_sort() { for (int i = n-1; i >= 1; i -- ) { bool flag = true; for (int j = 1; j <= i; j ++ ) if (a[j-1] > a[j]) { swap(a[j-1], a[j]); flag = false; } if (flag) return; } }
- 3.希尔排序:
void shell_sort() { for (int gap = n >> 1; gap; gap >>= 1) { for (int i = gap; i < n; i ++ ) { int x = a[i]; int j; for (j = i; j >= gap && a[j-gap] > x; j -= gap) a[j] = a[j-gap]; a[j] = x; } } }
- 4.堆排序:
void down(int u) { int t = u; if (u<<1 <= n && h[u<<1] < h[t]) t = u<<1; if ((u<<1|1) <= n && h[u<<1|1] < h[t]) t = u<<1|1; if (u != t) { swap(h[u], h[t]); down(t); } } int main() { for (int i = 1; i <= n; i ++ ) cin >> h[i]; for (int i = n/2; i; i -- ) down(i); while (true) { if (!n) break; cout << h[1] << ' '; h[1] = h[n]; n -- ; down(1); } return 0; }
- 5.计数排序:
void counting_sort() { int sorted[N]; int maxv = a[0]; for (int i = 1; i < n; i ++ ) if (maxv < a[i]) maxv = a[i]; int count[maxv+1]; for (int i = 0; i < n; i ++ ) count[a[i]] ++ ; for (int i = 1; i <= maxv; i ++ ) count[i] += count[i-1]; for (int i = n-1; i >= 0; i -- ) { sorted[count[a[i]]-1] = a[i]; count[a[i]] -- ; } for (int i = 0; i < n; i ++ ) a[i] = sorted[i]; }
这些模板可以直接套用,而且不存在边界问题。因为这是好多大佬用了很多的方法,实验搞出来的。