排序算法
简单排序
冒泡排序
冒泡排序,类似于水中冒泡,较大的数沉下去,较小的数慢慢冒起来,假设从小到大,即为较大的数慢慢往后排,较小的数慢慢往前排。
直观表达,每一趟遍历,将一个最大的数移到序列末尾。
void bubbleSort(int* array, int len) { for (int n = 0; n < len - 1; n++) { for (int m = 0; m < len - 1 - n; m++) { if (array[m] > array[m + 1]) { int tmp = array[m]; array[m] = array[m + 1]; array[m + 1] = tmp; } } } }
选择排序
每一次遍历待排序的序列,记录最小值的下标,和待排序第一个元素进行比较,如果小与待排序第一个元素,交换
void selectSort(vector<int>& arr) { for (int i = 0, k = 0; i < arr.size(); i++, k = i){ // 这一层查找后面最小值的下标 for (int j = i + 1; j < arr.size(); j++) { if (arr[k] > arr[j]) { k = j; } } // 交换值 if (i != k) { int temp = arr[i]; arr[i] = arr[k]; arr[k] = temp; } } }
插入排序
插入的意思是指将一个元素插入一个已排序序列中,选择好插入位置后,需要将插入位置以后的元素依次往后移动一个位置。
void InsertionSort(int *array, int N) { int j, k, key; for (j=1; j<N; j++) { key = array[j]; for (k=j-1; k>=0 && key<array[k]; k--) { array[k+1] = array[k]; } array[k+1] = key; } }
快速排序
在待排序序列中选择一个支点,以该支点为界,序列中比它小的元素放到它前面,比它大的元素放到它后面。
int partition(int *array, int p, int r) { int key = array[r]; int i = p; int j; for (j=p; j<r; j++) { if (array[j] <= key) swap(&array[i++], &array[j]); } swap(&array[i], &array[r]); return i; } void QSort(int *array, int p, int r) { int q; if (p < r) { q = partition(array, p, r); QSort(array, p, q-1); QSort(array, q+1, r); } }
归并排序
采用分治算法,
void merge(int* array, int p, int mid, int r) { vector<int> aux(r - p + 1); for (int k = p; k <= r; k++) { aux[k] = array[k]; } int i = p; // left: [p, mid] int j = mid + 1; // right: [mid + 1, r] for (int k = p; k <= r; k++) { if (i > mid) array[k] = aux[j++]; else if (j > r) array[k] = aux[i++]; else if (aux[j] < aux[i]) array[k] = aux[j++]; else array[k] = aux[i++]; } } void mergeSort(int* array, int p, int r) { if (p >= r) return; int mid = (p + r) / 2; mergeSort(array, p, mid); mergeSort(array, mid + 1, r); merge(array, p, mid, r); }
堆排序
计数排序
假设输入数组A[1,...,n]中的每一个元素都满足 0<=A[i]<=k,可以借助一个辅助的k维数组C[0,...,k]来对数组A排序,并将排序后的结果输出到数组B;
实现步骤:
1、找出待排序数组A中的最大和最小的元素;
2、统计数组A中每个值为i的元素出现的次数,存入数组C的第i项;
3、对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
4、反向填充目标数组B,将每个元素i放在B[C[i]],每放一个元素就将C[i]减1;
void CountingSort(int *A, int *B, int N) { int n = 0; const int K = 1 + max(A, N); int C[K]; for (n=0; n<K; n++) C[n] = 0; for (n=0; n<N; n++) C[A[n]]++; for (n=1; n<K; n++) C[n] += C[n-1]; for (n=N-1; n>=0; n--) { B[C[A[n]] - 1] = A[n]; C[A[n]]--; } }
桶排序
桶排序是将数组分到有限数量的桶子里,每个桶里面再分别排序。
当待排序数组内的数值服从均匀分布的时候,桶排序使用线性时间O(n)。
基数排序
思路:
1、将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零;
2、从低位开始,依次进行一次排序,这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
希尔排序
总结
(1) 上表中的简单排序包括除希尔排序之外的所有插入排序、冒泡排序和选择排序,其中以直接插入排序为最简单,当序列中的记录“基本有序”或 n值较小时,它是最佳排序方法。
(2) 从平均时间性能而言,快速排序最佳,其所需时间最省,但快速排序在最坏情况下的时间性能不如堆排序和归并排序。而后两者相比的结果是,在 n较大时,归并排序所需时间较堆排序省,但它所需的辅助存储量最多。
(3) 基数排序适用于n值很大而关键字较小的序列。若关键字也很大,而序列中大多数记录的“最高位关键字”均不同,则亦可先按“最高位关键字”不同将序列分成若干“小”的子序列,而后进行直接插入排序。
(4) 从方法的稳定性来比较,所有时间复杂度为 O(n^2)的简单排序法也是稳定的,然而,快速排序、堆排序和希尔排序等时间性能较好的排序方法都是不稳定的。
topK
问题描述:在数组A[p,...,r]中找到第k小的元素key,显然0<=k<=(r-p+1);
思路:可以借鉴快速排序的分治算法,如下
1、将A[p,...,r]划分成两个子数组A[p,...,q-1]和A[q+1,...,r];
2、然后判断第k小的元素key位于哪个子数组中;
3、如果k==(q-p+1),则返回key=A[q];
4、如果key位于第一个子数组,则继续在A[p,...,q-1]中求第k小的元素;
5、如果key位于第二个子数组,则在A[q+1,...,r]中求第(k-(q-p+1))小的元素;
int partition(int *array, int p, int r) { int key = array[r]; int i, j = p; for (i = p; i < r; i++) { if (array[i] <= key) { swap(&array[j++], &array[i]); } } swap(&array[j], &array[r]); return j; } int topk(int *array, int p, int r, int k) { if (p == r) return array[p]; int q = partition(array, p, r); if (p + k - 1 == q) { return array[q]; } else if (p + k - 1 > q) { return topk(array, q + 1, r, k - (q - p + 1)); } else { return topk(array, p, q - 1, k); } }
======专注高性能web服务器架构和开发=====