一. 选择排序 O(n^2) 不稳定
我的思路,但不是算法实现思路
这个排序,顾名思义,选择,选择,
第一步:从n个数中选择最小的那个,放到第0个位置*
第二步:再从 第1个数 到 最后一个数中,选择最小的,放到第1个位置
…
…
…
…
第N步:再从 第n-1个数 到 最后一个数中,选择最小的,放到第n-1个位置
template<typename T>
void selectionSort(T arr[], int n) {
for (int i = 0; i < n; i++) {
int minIndex=i; //将当前位置设置为最小索引
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIndex])
minIndex = j; //遍历第i个数之后的数,找出最小的数的索引
}
swap(arr[i], arr[minIndex]); //将当前的数和最小的数进行交换
//这里用的是c++ swap()是std里自带的
//建议自己写一个,因为c++自带的swap()比自己写的复杂,所以运行时间慢一点。
}
}
个人感觉:自己写的出来就好,没什么卵用
二. 冒泡排序 O(n^2) 稳定
我的思路
有n个泡泡,将它们随机变成串成一串冰糖葫芦
从上到下,并给它们标上序号,序号大小,从上依次递减
0…n-1
我们选择最后一个泡泡,将它与它前一个泡泡比较大小,如果小,
那么,就将两个泡泡对换.
直到当前的泡泡比前面的泡泡大。
抛弃当前的泡泡。
换前一个泡泡当主角。
在循环上一操作。
唉。表述的太烂了。
template<typename T>
void bubbleSort(T arr[], int n) {
int newn; // 使用newn进行优化
do {
newn = 0;
for (int i = 1; i < n; i++)
if (arr[i - 1] > arr[i]) {
swap(arr[i - 1], arr[i]);
// 记录最后一次的交换位置,在此之后的元素在下一轮扫描中均不考虑
newn = i;
}
n = newn;
} while (newn > 0);
}
其实没什么用啦
三. 插入排序 O(n^2) 稳定
我的思路
给你一排数字,从第二数字开始 ,和之前的数字进行比较,按照大小插入到合适的位置。
比如 4,5,9,1,4,2,6,9,2,4
第一次排序后: 4,5,9,1,4,2,6,9,2,4
第二次排序后: 4,5,9,1,4,2,6,9,2,4
第三次排序后: 1,4,5,9,4,2,6,9,2,4
直到遍历完。
template <typename T>
void insertionSort(T arr[], int n) {
for (int i = 1; i < n; i++) {
//i从第二个元素开始
int e = arr[i]; //存储要交换位置的元素
int j; //用j存储要交换的位置
for (j = i; j > 0 && arr[j - 1] > e; j--) {//j最小为1
arr[j] = arr[j - 1];
}//此时的j位置正好是大于等于前一个数,小于后一个数的
arr[j] = e; //最后一步插入;
}
}
提示:
1.插入排序在处理近乎有序数组的时候非常快,可以进化成O(n)的时间复杂度。
2.可以用来进行优化
蛮重要的
四. 希尔排序 O(n^1.3) 不稳定
我的思路
希尔排序是插入排序的升级版
有一组数:26,44,18,58,28,21,67,79,25,26,98,55
希尔排序是将它们分组后,对每组进行插入排序。
那分组怎么分呢?
可以选择一个初值,比如将组距设置为3,然后为2,最后为1
组距为3: 26,58,67,26
44,28,79,98
18,21,25,55
58,67,26
28,79,98
21,25,55,
67,26
79,98
25,55
分别对每组进行插入排序
组距为2:类似
最后组距为1,这是这一组数据已经近乎有序了
我们都知道插入排序对近乎有序的数组的时间复杂度为O(n);
这样性能就会有所提升。
template <typename T>
void shellSort(T arr[], int n) {
int h = 1; //组距设置为1
while (h < n / 3) //根据某种推论,组距为3的倍数时,算法性能高一点
h = h * 3 + 1;
while (h >= 1) {//h最小为1
for (int i = h; i < n; i++) { //插入排序
T e = arr[i];
int j;
for (j = i; j >= h&&e < arr[j - h]; j -= h)
arr[j] = arr[j - h];
arr[j] = e;
}
h /= 3; //每次除以3 知道h=1
}
}
五. 归并排序 O(nlogn) 稳定
我的思路
把一排数一分为二,在把分好的二分之一,再一分为二,直到最小为1。
然后逐层往上,对每层进行归并排序。(瞎讲)
讲起来费力,不讲了,直接上代码。
template<typename T>
void insertionSort(T arr[], int l, int r) {
for (int i = l + 1; i <=r; i++) {
T e = arr[i];
int j;
for (j = i; j > l&&e < arr[j - 1]; j--)
arr[j] = arr[j - 1];
arr[j] = e;
}
}
template <typename T>
void __merge(T arr[], int l, int mid, int r) {
T* aux = new T[r - l + 1];//创建一个临时的空间
for (int i = l; i <= r; i++) {
aux[i - l] = arr[i];//复制arr
}
int i = l; int j = mid + 1;
for (int k =l; k <= r; k++) {
if (i > mid) {
arr[k] = aux[j - l];
j++;
}
else if (j > r) {
arr[k] = aux[i - l];
i++;
}
else if (aux[i - l] > aux[j - l]) {
arr[k] = aux[j - l];
j++;
}
else {
arr[k] = aux[i - l];
i++;
}
}
delete[] aux;
}
template <typename T>
void __mergeSort(T arr[], int l, int r) {
if (r - l <= 15) {//用插入排序对小数组进行优化
insertionSort(arr, l, r);
return;
}
int mid = (l + r) / 2;
__mergeSort(arr, l, mid);//对左边归并
__mergeSort(arr, mid + 1, r);//对右边归并
if(arr[mid]>arr[mid+1])//如果左边的最大值小于右边的最小值,那么就省去这一步,在有序的数组中很有用。
__merge(arr, l, mid, r);//进行排序
}
template <typename T>
void mergeSort(T arr[], int n)
{
__mergeSort(arr, 0, n - 1);
}
还有一个自底向上的归并排序
这个不需要递归,只需要迭代
template<typename T >
//buttom up
void mergeSortBU(T arr[], int n) {
for (int sz = 1; sz <= n; sz += sz) {sz就是每一组的大小,之后每次乘2
for (int i =0; i+sz<n; i+=sz+sz) {
__merge(arr, i, i + sz - 1, min(i + sz + sz - 1,n-1));//i+sz+sz-1可能大于n-1
}
}
}
六. 快速排序 O(nlogn) 不稳定
template<typename T>
int __partition(T arr[], int l, int r) {
swap(arr[l], arr[rand() % (r - l + 1) + l]);//从区间里随机找一个数
T v = arr[l];//要比较的元素
int j=l;//指向第一个位置
for (int i = l+1; i <= r; i++) {
if (arr[i] < v) {
swap(arr[++j], arr[i]);
}
}
swap(arr[l], arr[j]);
return j;
}
template<typename T>
void __quickSort(T arr[], int l, int r) {
if (l >= r)return;
int p = __partition(arr, l, r);
__quickSort(arr, l, p-1);
__quickSort(arr, p + 1, r);
}
template<typename T>
void quickSort(T arr[], int n) {
__quickSort(arr, 0, n - 1);
}
对于有许多重复键值的用双路快排和三路快排
双路
template<typename T>
int partition2(T arr[], int l, int r) {
swap(arr[l], arr[rand() % (r - l + 1) + l]);
T v = arr[l];
int i = l + 1;
int j = r;
//[l+1...i).......(j...r]
while (true) {
while (i <= r && arr[i] < v)i++;
while (j >=l + 1 && arr[j] > v)j--;
if (i > j)break;
swap(arr[i], arr[j]);
i++;
j--;
}
swap(arr[l], arr[j]);
return j;
}
template<typename T>
void __quickSort2(T arr[], int l, int r) {
if (r - l <= 15) {
insertionSort(arr, l, r);//做一个小优化
return;
}
int p = partition2(arr, l, r);
__quickSort2(arr, l, p - 1);
__quickSort2(arr, p + 1, r);
}
template<typename T>
void quickSort2(T arr[], int n) {
srand(time(NULL));
__quickSort2(arr, 0, n - 1);
}
三路(在有重复键值的数中,三路比双路快)
template<typename T>
void __partition(T arr[], int l, int r) {
if (l >= r)
return;
swap(arr[l], arr[rand() % (r - l + 1) + l]);
T v = arr[l];
int lt = l;
int gt = r + 1;
int i = l + 1;
while (i < gt) {
if (arr[i] < v) {
swap(arr[++lt], arr[i]);
i++;
}
else if (arr[i] > v) {
swap(arr[--gt], arr[i]);
}
else {
i++;
}
}
swap(arr[l], arr[lt]);
__partition(arr, l, lt - 1);
__partition(arr,gt,r);
}
template<typename T>
void quickSort3(T arr[], int n) {
srand(time(NULL));
__partition(arr, 0, n - 1);
}
这是我自己实验下来的结果(100万个数排序)
1.在有重复键值的数组中,三路快排最快,(重要)
2.在随机数组中快排比归并快一点,(不重要)
3.在近乎有序的数组中,双路和归并快一点。(不重要)
七. 堆排序 O(nlogn) 稳定
template <typename T>
void shiftDown(T arr[], int n, int k) {
while (2 * k + 1 < n) {
int j = 2 * k + 1;
if (j + 1 < n&&arr[j + 1] > arr[j])
j+=1;
if (arr[k] >= arr[j])break;
swap(arr[k], arr[j]);
k = j;
}
}
template <typename T>
void heapSort(T arr[], int n) {
for (int i = (n - 1) / 2; i >=0; i--)
shiftDown(arr, n, i);
for (int i = n - 1; i > 0; i--)
{
swap(arr[0], arr[i]);
shiftDown(arr, i, 0);
}
}