排序算法的总结
1. 概述
在数据结构中常见的排序算法有八种,大体可概括如下
这些排序算法的性能分析如下图
其中,算法的稳定性是指多个相同值的相对位置也许会在算法结束时产生变动
2. 排序算法
2.1 交换排序
交换排序的对象是序列中的两个元素,基本操作是目标元素位置的互换,它的特点是键值大的元素向后移动,键值小的元素向前移动。
交换排序算法主要有两种:冒泡排序和快速排序
2.1.1 冒泡排序
(一)思想
冒泡排序的思想是通过两层循环遍历序列,每次遍历都会使无序区中最大的元素通过不断地交换直到序列尾部的有序区(就像冒泡一样,一点一点浮到水面)
(二)C++实现
void bubble_sort(int a[], int n) { int i, j, temp; for (j = 0; j < n - 1; j++) for (i = 0; i < n - 1 - j; i++) { if(a[i] > a[i + 1]) { temp = a[i]; a[i] = a[i + 1]; a[i + 1] = temp; } } }
2.1.2 快速排序
(一)思想
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
(二)C++实现
void Qsort(int arr[], int low, int high){ if (high <= low) return; int i = low; int j = high + 1; int key = arr[low]; while (true) { /*从左向右找比key大的值*/ while (arr[++i] < key) { if (i == high){ break; } } /*从右向左找比key小的值*/ while (arr[--j] > key) { if (j == low){ break; } } if (i >= j) break; /*交换i,j对应的值*/ int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } /*中枢值与j对应值交换,将其换到中间位置,左边所有的数都小于它,右边所有的数都大于它*/ int temp = arr[low]; arr[low] = arr[j]; arr[j] = temp; Qsort(arr, low, j - 1); Qsort(arr, j + 1, high); }
2.2 插入排序
插入排序的对象是某个元素,基本操作就是取出元素和插入到某一位置,插入排序的特点是不断的维护和扩大有序区间,直至完成对整个队列的排序。
2.2.1 直接插入排序
(一)思想
不断地从无序区取出元素插入到有序区,并最终完成对整个序列的排序。
(二)C++实现
void insert_sort(int a[], int n) { int i, j, k; for (i = 1; i < n; i++) { //为a[i]在前面的a[0...i-1]有序区间中找一个合适的位置 for (j = i - 1; j >= 0; j--) if (a[j] < a[i]) break; //如找到了一个合适的位置 if (j != i - 1) { //将比a[i]大的数据向后移 int temp = a[i]; for (k = i - 1; k > j; k--) a[k + 1] = a[k]; //将a[i]放到正确位置上 a[k + 1] = temp; } } }
2.2.2 希尔(Shell)排序
(一)思想
希尔排序是直接插入排序的改进版本,通过划分小区间并对小区间使用直接插入排序,并不断地减小划分的区间数(每个区间元素越来越多)直至只有一个区间(即原队列)。
(二)C++实现
void ShellSort(int a[], int n) { int i,j; int jump = n; do { jump = jump/3 + 1; for(i=jump+1;i<n;i++) { if (a[i]<a[i-jump]) { a[0] = a[i]; for (j=i-jump;j>0&&a[0]<a[j];j-=jump) { a[j+jump] = a[j]; } a[j+jump] = a[0]; } } } while (jump>1); }
2.3 选择排序
选择排序的基本对象是满足性质要求的某元素(如最大元素或最小元素),基本操作是是对此元素的移动,通过对满足要求元素的不断选择和移动,实现队列的排序。
2.3.1 简单选择排序
(一)思想
简单选择排序每次遍历待排序队列(初始为整个队列)找到最小元素,放至待排序列的队首,然后缩小待排序列的长度,直至队列排序完成。
(二)C++实现
void select_sort(int a[],int n) { int i,j,min; for(int i=0;i<n-1;i++)//i为已排序序列的末尾 { min=i; for(int j=i+1;j<n;j++)//未排序序列 if(a[j]<a[min])//找出未排序序列中的最小值 min=j; if(min!=i) swap(a[i],a[min]);//放到已排序序列的末尾,该操作很有可能把稳定性打乱,所以选择排序是不稳定的排序算法 } }
2.3.2 堆排序
(一)思想
2.3.2.1 堆
堆排序是我认为排序算法里面最复杂难懂的一个,首先我们了解一下什么是堆:
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:
同时,对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子
该数组从逻辑上讲就是一个堆结构,我们用简单的公式来描述一下堆的定义就是:
大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
2.3.2.2 堆排序
接下来再来说堆排序
堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了
升序——大顶堆 降序——小顶堆
(二)C++实现
/* * (最大)堆的向下调整算法 * * 注:数组实现的堆中,第N个节点的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。 * 其中,N为数组下标索引值,如数组中第1个数对应的N为0。 * * 参数说明: * a -- 待排序的数组 * start -- 被下调节点的起始位置(一般为0,表示从第1个开始) * end -- 截至范围(一般为数组中最后一个元素的索引) */ void maxheap_down(int a[], int start, int end) { int c = start; // 当前(current)节点的位置 int l = 2*c + 1; // 左(left)孩子的位置 int tmp = a[c]; // 当前(current)节点的大小 for (; l <= end; c=l,l=2*l+1) { // "l"是左孩子,"l+1"是右孩子 if ( l < end && a[l] < a[l+1]) l++; // 左右两孩子中选择较大者,即m_heap[l+1] if (tmp >= a[l]) break; // 调整结束 else // 交换值 { a[c] = a[l]; a[l]= tmp; } } } /* * 堆排序(从小到大) * * 参数说明: * a -- 待排序的数组 * n -- 数组的长度 */ void heap_sort_asc(int a[], int n) { int i; // 从(n/2-1) --> 0逐次遍历。遍历之后,得到的数组实际上是一个(最大)二叉堆。 for (i = n / 2 - 1; i >= 0; i--) maxheap_down(a, i, n-1); // 从最后一个元素开始对序列进行调整,不断的缩小调整的范围直到第一个元素 for (i = n - 1; i > 0; i--) { // 交换a[0]和a[i]。交换后,a[i]是a[0...i]中最大的。 swap(a[0], a[i]); // 调整a[0...i-1],使得a[0...i-1]仍然是一个最大堆。 // 即,保证a[i-1]是a[0...i-1]中的最大值。 maxheap_down(a, 0, i-1); } }
2.4 归并排序
(一)思想
归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
(二)C++实现
void merge_sort(int *data, int start, int end, int *result) { if(1 == end - start)//如果区间中只有两个元素,则对这两个元素进行排序 { if(data[start] > data[end]) { int temp = data[start]; data[start] = data[end]; data[end] = temp; } return; } else if(0 == end - start)//如果只有一个元素,则不用排序 return; else { //继续划分子区间,分别对左右子区间进行排序 merge_sort(data,start,(end-start+1)/2+start,result); merge_sort(data,(end-start+1)/2+start+1,end,result); //开始归并已经排好序的start到end之间的数据 merge(data,start,end,result); //把排序后的区间数据复制到原始数据中去 for(int i = start;i <= end;++i) data[i] = result[i]; } } void merge(int *data,int start,int end,int *result) { int left_length = (end - start + 1) / 2 + 1;//左部分区间的数据元素的个数 int left_index = start; int right_index = start + left_length; int result_index = start; while(left_index < start + left_length && right_index < end+1) { //对分别已经排好序的左区间和右区间进行合并 if(data[left_index] <= data[right_index]) result[result_index++] = data[left_index++]; else result[result_index++] = data[right_index++]; } while(left_index < start + left_length) result[result_index++] = data[left_index++]; while(right_index < end+1) result[result_index++] = data[right_index++]; }
2.5 基数排序
(一)思想
基数排序(Radix Sort)是桶排序的扩展,它的基本思想是:将整数按位数切割成不同的数字,然后按每个位数分别比较。
具体做法是:将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
通过基数排序对数组{53, 3, 542, 748, 14, 214, 154, 63, 616},它的示意图如下:
(二)C++实现
/* * 获取数组a中最大值 * * 参数说明: * a -- 数组 * n -- 数组长度 */ int get_max(int a[], int n) { int i, max; max = a[0]; for (i = 1; i < n; i++) if (a[i] > max) max = a[i]; return max; } /* * 对数组按照"某个位数"进行排序(桶排序) * * 参数说明: * a -- 数组 * n -- 数组长度 * exp -- 指数。对数组a按照该指数进行排序。 * * 例如,对于数组a={50, 3, 542, 745, 2014, 154, 63, 616}; * (01) 当exp=1表示按照"个位"对数组a进行排序 * (02) 当exp=10表示按照"十位"对数组a进行排序 * (03) 当exp=100表示按照"百位"对数组a进行排序 * ... */ void count_sort(int a[], int n, int exp) { int output[n]; // 存储"被排序数据"的临时数组 int i, buckets[10] = {0}; // 将数据出现的次数存储在buckets[]中 for (i = 0; i < n; i++) buckets[ (a[i]/exp)%10 ]++; // 更改buckets[i]。目的是让更改后的buckets[i]的值,是该数据在output[]中的位置。 for (i = 1; i < 10; i++) buckets[i] += buckets[i - 1]; // 将数据存储到临时数组output[]中 for (i = n - 1; i >= 0; i--) { output[buckets[ (a[i]/exp)%10 ] - 1] = a[i]; buckets[ (a[i]/exp)%10 ]--; } // 将排序好的数据赋值给a[] for (i = 0; i < n; i++) a[i] = output[i]; } /* * 基数排序 * * 参数说明: * a -- 数组 * n -- 数组长度 */ void radix_sort(int a[], int n) { int exp; // 指数。当对数组按各位进行排序时,exp=1;按十位进行排序时,exp=10;... int max = get_max(a, n); // 数组a中的最大值 // 从个位开始,对数组a按"指数"进行排序 for (exp = 1; max/exp > 0; exp *= 10) count_sort(a, n, exp); }