排序有几种方式,分别介绍其实现原理?(问)
(1)冒泡排序
void bubble_sort(int arr[], int len) {
int i, j;
for (i = 0; i < len; i++)
for (j = len-1; j > i; j--)
if (arr[j] > arr[j - 1])
swap(arr[j], arr[j - 1]);
}
(2) 选择排序
void selection_sort(int arr[], int len) {
int i, j, min;
for (i = 0; i < len - 1; i++) {
min = i;
for (j = i + 1; j < len; j++)
if(arr[min] > arr[j])
min = j;
if(min != i)
swap(arr[i], arr[min]);
}
}
(3) 快排(挖坑排序)(重要)
1. 右指针找比基准数小的,左指针找比基准数大的,交换之
2. 冒泡+二分+递归分治
void quickSort(int s[], int left, int right) {
if (left < right) {
int i = left, j = right, x = s[left];
while (i < j) {
while(i < j && s[j]>= x) // 从右向左找第一个小于x的数
j--;
if(i < j)
s[i++] = s[j];
while(i < j && s[i]< x) // 从左向右找第一个大于等于x的数
i++;
if(i < j)
s[j--] = s[i];
}
s[i] = x;
quickSort(s, left, i - 1); // 递归调用
quickSort(s, i + 1, right);
}
}
为什么一定要j指针先动呢?首先这也不是绝对的,这取决于基准数的位置,因为在最后两个指针相遇的时候,要交换基准数到相遇的位置。一般选取第一个数作为基准数,那么就是在左边,所以最后相遇的数要和基准数交换,那么相遇的数一定要比基准数小。所以j指针先移动才能先找到比基准数小的数。
(4) 插入排序
插入排序是通过比较找到合适的位置插入元素来达到排序的目的。举个栗子,对5,3,8,6,4这个无序序列进行简单插入排序,首先假设第一个数的位置时正确的,想一下在拿到第一张牌的时候,没必要整理。然后3要插到5前面,把5后移一位,变成3,5,8,6,4.想一下整理牌的时候应该也是这样吧。然后8不用动,6插在8前面,8后移一位,4插在5前面,从5开始都向后移一位。注意在插入一个数的时候要保证这个数前面的数已经有序。
void insertion_sort(int arr[], int len) {
int i, j, temp;
for (i = 1; i < len; i++) {
temp = arr[i];
for (j=i-1; j>=0 && arr[j]>temp; j--) //一直后移
arr[j+1] = arr[j];
arr[j+1] = temp;
}
}
(5) 希尔排序(分组+直接插入)
/*希尔排序:先将整个待排元素序列分割成若干子序列(由相隔某个“增量”的元素组成的)
分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序
(增量足够小)时,再对全体元素进行一次直接插入排序(增量为1)。其时间复杂度
为O(n^3/2),要好于直接插入排序的O(n^2) */
void Shell_sort(int a[],size_t n) {
int i, j, k, gap;
for (gap = n/2; gap > 0; gap /= 2) { //增量序列为n/2,n/4.直到1
for (i = 0; i < gap; ++i) {
for (j = i+ gap; j < n; j += gap) { //对每个分组插入排序
if (a[j - gap] > a[j]) { //对a[j]进行插入排序
int temp = a[j];
k = j - gap;
while (k>=0 && a[k]>temp) {
a[k+ gap] = a[k];
k -= gap;
}
a[k+gap] = temp;
}
}
}
}
}
(6)归并排序
/*假设初始序列含有n个记录,则可以看成n个有序的子序列,每个子序列的长度为1,
然后两两归并,得到(不小于n/2的最小整数)个长度为2或1的有序子序列,再两两归并,
...如此重复,直至得到一个长度为n的有序序列为止,这种排序方法称为2路归并排序.
时间复杂度为O(nlogn),空间复杂度为O(n+logn),如果非递归实现归并,则避免了递
归时深度为logn的栈空间, 空间复杂度为O(n) */
/*lpos is the start of left half, rpos is the start of right half*/
void merge(int a[], int tmp_array[], int left_start, int right_start, int right_end){
int i, left_end, num_elements, tmpos;
left_end = right_start - 1;
tmpos = left_start;
num_elements = right_end – left_start + 1;
while (left_start <= left_end && right_start <= right_end)
if (a[left_start] <= a[right_start])
tmp_array[tmpos++] = a[left_start++];
else
tmp_array[tmpos++] = a[right_start++];
while (left_start <= left_end) /*copy rest of the first part*/
tmp_array[tmpos++] = a[left_start++];
while (right_start<=right_end)/*copy rest of the second part*/
tmp_array[tmpos++] = a[right_start++];
/*copy array back*/
for (i = 0; i < num_elements; i++, rightn--)
a[right_end] = tmp_array[right_end];
}
void msort(int a[], int tmp_array[], int left, int right){
int center;
if (left < right){
center = (right + left) / 2;
msort(a, tmp_array, left, center);
msort(a, tmp_array, center + 1, right);
merge(a, tmp_array, left, center + 1, right);
}
}
void merge_sort(int a[], int n){
int *tmp_array;
tmp_array = (int *)malloc(n * sizeof(int));
if (tmp_array != NULL){
msort(a, tmp_array, 0, n - 1);
free(tmp_array);
}
else {
free(tmp_array);
printf("No space for tmp array!\n");
}
}
非递归实现:
void merge_sort(int arr[], int len) {
int* a = arr;
int* b = new int[len];
for (int seg = 1; seg < len; seg += seg) {
for (int start=0; start<len; start += seg+seg) {
int low=start, mid = min(start+seg, len);
int high = min(start+seg+seg, len), k = low;
int start1 = low, end1 = mid;
int start2 = mid, end2 = high;
while (start1 < end1 && start2 < end2)
b[k++] = a[start1] < a[start2] ? a[start1++] : a[start2++];
while (start1 < end1)
b[k++] = a[start1++];
while (start2 < end2)
b[k++] = a[start2++];
}
int* temp = a;
a = b;
b = temp;
}
if (a != arr) {
for (int i = 0; i < len; i++)
b[i] = a[i];
b = a;
}
delete[] b;
}
(7)堆排序
堆排序的基本思想就是:从最大(小)堆得顶部不断取走堆顶元素放到有序序列中,直到堆的元素被全部取完。堆排序完全依赖于最大(小)堆的相关操作。
A. 堆的插入
(1) 将新数据放在数组最后。可以发现从这个新数据的父结点到根结点必然为一个有序的数列.
(2) 将这个新数据插入到这个有序数据中——这就类似于直接插入排序中将一个数据并入到有序区间中.
//新加入i结点,其父结点为(i - 1) / 2,调整堆
void MinHeapFixup(int a[], int i) {
int j = (i-1)/2, temp = a[i]; //父结点
while (j >= 0 && i != 0) {
if (a[j] <= temp)
break;
a[i] = a[j]; //把较大的子结点往下移动,替换它的子结点
i = j; j = (i - 1) / 2;
}
a[i] = temp;
}
B. 堆的删除
按定义,堆中每次都只能删除第0个数据。为了便于重建堆,实际的操作是将最后一个数据的值赋给根结点,然后再从根结点开始进行一次从上向下的调整。调整时先在左右儿子结点中找最小的,如果父结点比这个最小的子结点还小说明不需要调整了,反之将父结点和它交换后再考虑后面的结点。相当于从根结点将一个数据的“下沉”过程。
// 从i节点开始调整, n为节点总数,从0开始计算
void MinHeapFixdown(int a[], int i, int n) {
int j = 2 * i + 1, temp = a[i];
while (j < n) {
if (j + 1 < n && a[j + 1] < a[j]) //在左右孩子中找最小的
j++;
if (a[j] >= temp)
break;
a[i] = a[j]; //把较小的子结点往上移动,替换它的父结点
i = j; j = 2 * i + 1;
}
a[i] = temp;
}
C.堆化数组
有了堆的插入和删除后,再考虑下如何对一个数据进行堆化操作。要一个一个的从数组中取出数据来建立堆吧,不用!先看一个数组,如下图:
很明显,对叶子结点来说,可以认为它已经是一个合法的堆了即20,60, 65, 4, 19都分别是一个合法的堆。只要从A[4]=50开始向下调整就可以了。然后再取A[3]=30,A[2] = 17,A[1] = 12,A[0] = 9分别作一次向下调整操作就可以了。下图展示了这些步骤:
写出堆化数组的代码:
//建立最小堆
void MakeMinHeap(int a[], int n) {
for (int i = n / 2 - 1; i >= 0; i--)
MinHeapFixdown(a, i, n);
}
D.堆排序
首先可以看到堆建好之后堆中第0个数据是堆中最小的数据。取出这个数据再执行下堆的删除操作。这样堆中第0个数据又是堆中最小的数据,重复上述步骤直至堆中只有一个数据时就直接取出这个数据。
void MinheapsortTodescendarray(int a[], int n){
for (int i = n - 1; i >= 1; i--){
Swap(a[i], a[0]);
MinHeapFixdown(a, 0, i);
}
}
由于堆也是用数组模拟的,故堆化数组后,第一次将A[0]与A[n - 1]交换,再对A[0…n-2]重新恢复堆。第二次将A[0]与A[n – 2]交换,再对A[0…n - 3]重新恢复堆,重复这样的操作直到A[0]与A[1]交换。由于每次都是将最小的数据并入到后面的有序区间,故操作完成后整个数组就有序了。有点类似于直接选择排序。
注意使用最小堆排序后是递减数组,要得到递增数组,可以使用最大堆。
由于每次重新恢复堆的时间复杂度为O(logN),共N - 1次重新恢复堆操作,再加上前面建立堆时N / 2次向下调整,每次调整时间复杂度也为O(logN)。二次操作时间相加还是O(N * logN)。故堆排序的时间复杂度为O(N * logN)。
(8)计数排序
对已知范围的应用排序。而且这个范围不能太大。它创建一个长度为这个数据范围的数组C,C中每个元素记录要排序数组中对应记录的出现个数。
假设要排序的数组为 A = {1,0,3,1,0,1,1},这里最大值为3,最小值为0,那么我们创建一个数组C,长度为4.然后一趟扫描数组A,得到A中各个元素的总数,并保持到数组C的对应单元中。比如0 的出现次数为2次,则 C[0] = 2;1 的出现次数为4次,则C[1] = 4
由于C 是以A的元素为下标的,所以这样一做,A中的元素在C中自然就成为有序的了,这里我们可以知道 顺序为 0,1,3 (2 的计数为0)。然后我们把这个在C中的记录按每个元素的计数展开到输出数组B中,排序就完成了。也就是 B[0] 到 B[1] 为0 B[2] 到 B[5] 为1 这样依此类推。
这种排序算法,依靠一个辅助数组来实现,不基于比较,算法复杂度为 O(n) ,但由于要一个辅助数组C,所以空间复杂度要大一些,由于计算机的内存有限,这种算法不适合范围很大的数的排序。
注:基于比较的排序算法的最佳平均时间复杂度为 O(nlogn)
void Sort(int[] A, int k) {
int[] C = new int[k + 1];
for (int j = 0; j < A.Length; j++)
C[A[j]]++;
int z = 0;
for (int i = 0; i <= k; i++){
while (C[i]-- > 0)
A[z++] = i;
}
}
由于C数组下标 i 就是A 的值,所以我们不需要保留A中原来的数了,这个代码减少了一个数组B,而且要比原来的代码简化了很多。
(9)基数排序
按位数从最低位开始排序
http://www.cnblogs.com/weixliu/archive/2012/12/23/2829671.html