如果说各种编程语言是程序员的招式,那么数据结构和算法就相当于程序员的内功。
想写出精炼、优秀的代码,不通过不断的锤炼,是很难做到的。
排序算法作为数据结构的重要部分,系统地学习一下是很有必要的。
排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整为“有序”的记录序列。
排序分为内部排序和外部排序。
若整个排序过程不需要访问外存便能完成,则称此类排序问题为内部排序。
反之,若参加排序的记录数量很大,整个序列的排序过程不可能在内存中完成,则称此类排序问题为外部排序。
八大排序算法均属于内部排序。如果按照策略来分类,大致可分为:交换排序、插入排序、选择排序、归并排序和基数排序。如下图所示:

1.插入排序
*直接插入排序
*希尔排序
2.选择排序
*简单选择排序
*堆排序
3.交换排序
*冒泡排序
*快速排序
4.归并排序
5.基数排序
不稳定排序:简单选择排序,快速排序,希尔排序,堆排序
稳定排序:冒泡排序,直接插入排序,归并排序,奇数排序
1、插入排序
将第一个和第二个元素排好序,然后将第3个元素插入到已经排好序的元素中,依次类推(插入排序最好的情况就是数组已经有序了)
2、希尔排序
因为插入排序每次只能操作一个元素,效率低。元素个数N,取奇数k=N/2,将下标差值为k的数分为一组(一组元素个数看总元素个数决定),在组内构成有序序列,再取k=k/2,将下标差值为k的数分为一组,构成有序序列,直到k=1,然后再进行直接插入排序。
3、简单选择排序
选出最小的数和第一个数交换,再在剩余的数中又选择最小的和第二个数交换,依次类推
4、堆排序
以升序排序为例,利用小根堆的性质(堆顶元素最小)不断输出最小元素,直到堆中没有元素
1.构建小根堆
2.输出堆顶元素
3.将堆低元素放一个到堆顶,再重新构造成小根堆,再输出堆顶元素,以此类推
5、冒泡排序
改进1:如果某次冒泡不存在数据交换,则说明已经排序好了,可以直接退出排序
改进2:头尾进行冒泡,每次把最大的沉底,最小的浮上去,两边往中间靠1
6、快速排序
选择一个基准元素,比基准元素小的放基准元素的前面,比基准元素大的放基准元素的后面,这种动作叫分区,每次分区都把一个数列分成了两部分,每次分区都使得一个数字有序,然后将基准元素前面部分和后面部分继续分区,一直分区直到分区的区间中只有一个元素的时候,一个元素的序列肯定是有序的嘛,所以最后一个升序的序列就完成啦。
7、归并排序
将一个无序的数列一直一分为二,直到分到序列中只有一个数的时候,这个序列肯定是有序的,因为只有一个数,然后将两个只含有一个数字的序列合并为含有两个数字的有序序列,这样一直进行下去,最后就变成了一个大的有序数列
8、基数排序
找到最大的数,开个比最大的数大一点的数组,遍历每个元素,某个元素为k,则a[k]++,最好遍历数组a,a[k]等于多少就输出多少个k。只能处理整型数

下面针对不同排序进行一一讲解。
一、直接插入排序(Insertion Sort)
算法思想:
直接插入排序的核心思想就是:将数组中的所有元素依次跟前面已经排好的元素相比较,如果选择的元素比已排序的元素小,则交换,直到全部元素都比较过 因此,从上面的描述中我们可以发现,直接插入排序可以用两个循环完成:
第一层循环:遍历待比较的所有数组元素
第二层循环:将本轮选择的元素(selected)与已经排好序的元素(ordered)相比较。如果:selected > ordered,那么将二者交换。

算法代码:
二、希尔排序(Shell’ s Sort) 算法思想: 希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。 希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。 算法步骤: 1.选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1; 2.按增量序列个数k,对序列进行k 趟排序; 3.每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。 算法代码: 三、简单选择排序(Selection Sort) 算法思想: 简单选择排序的实现思想:比较+交换 算法代码: 四、堆排序(Heap Sort) 算法思想: 堆的概念 堆:本质是一种数组对象。特别重要的一点性质:任意的叶子节点小于(或大于)它所有的父节点。对此,又分为大顶堆和小顶堆: 大顶堆要求节点的元素都要大于其孩子。 小顶堆要求节点元素都小于其左右孩子。 两者对左右孩子的大小关系不做任何要求。 利用堆排序,就是基于大顶堆或者小顶堆的一种排序方法。下面,我们通过大顶堆来实现。 基本思想:堆排序可以按照以下步骤来完成: 1.首先将序列构建称为大顶堆;(这样满足了大顶堆那条性质:位于根节点的元素一定是当前序列的最大值) 2. 取出当前大顶堆的根节点,将其与序列末尾元素进行交换;(此时:序列末尾的元素为已排序的最大值;由于交换了元素,当前位于根节点的堆并不一定满足大顶堆的性质) 3. 对交换后的n-1个序列元素进行调整,使其满足大顶堆的性质; 4. 重复2.3步骤,直至堆中只有1个元素为止 下面是基于大顶堆的堆排序算法代码: 五、冒泡排序(Bubble Sort) 算法思想: 冒泡遍历所有的数据,每次对相邻元素进行两两比较,如果顺序和预先规定的顺序不一致,则进行位置交换;这样一次遍历会将最大或最小的数据上浮到顶端,之后再重复同样的操作,直到所有的数据有序。这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端。 算法代码: 六、快速排序(Quick Sort) 算法思想: 快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要Ο(n logn)次比较。在最坏状况下则需要Ο(n2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他Ο(n log n) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来 快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。 算法步骤: 递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。 算法代码:void print(int a[], int n ,int i){ cout< for(int j= 0; j<8; j++){ cout< } cout<} void InsertSort(int a[], int n){ for(int i= 1; i if(a[i] < a[i-1]){ //若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入 int j= i-1; int x = a[i]; //复制为哨兵,即存储待排序元素 a[i] = a[i-1]; //先后移一个元素 while(x < a[j]){ //查找在有序表的插入位置 a[j+1] = a[j]; j--; //元素后移 } a[j+1] = x; //插入到正确位置 } print(a,n,i); //打印每趟排序的结果 }}int main{ int a[8] = {3,1,5,7,2,4,9,6}; InsertSort(a,8); print(a,8,8);}
void print(int a[], int n ,int i){ cout< for(int j= 0; j<8; j++){ cout< } cout<}/** * 直接插入排序的一般形式 * * @param int dk 缩小增量,如果是直接插入排序,dk=1 * */void ShellInsertSort(int a[], int n, int dk){ for(int i= dk; i if(a[i] < a[i-dk]){ //若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入 int j = i-dk; int x = a[i]; //复制为哨兵,即存储待排序元素 a[i] = a[i-dk]; //首先后移一个元素 while(x < a[j]){ //查找在有序表的插入位置 a[j+dk] = a[j]; j -= dk; //元素后移 } a[j+dk] = x; //插入到正确位置 } print(a, n,i ); }} // 先按增量d(n/2,n为要排序数的个数进行希尔排序void shellSort(int a[], int n){ int dk = n/2; while( dk >= 1 ){ ShellInsertSort(a, n, dk); dk = dk/2; }}int main{ int a[8] = {3,1,5,7,2,4,9,6}; //ShellInsertSort(a,8,1); //直接插入排序 shellSort(a,8); //希尔插入排序 print(a,8,8);}
void print(int a[], int n ,int i){ cout<<"第"< for(int j= 0; j<8; j++){ cout< } cout<}/** * 数组的最小值 * * @return int 数组的键值 */int SelectMinKey(int a[], int n, int i){ int k = i; for(int j=i+1 ;j< n; ++j) { if(a[k] > a[j]) k = j; } return k;}/** * 选择排序 * */void selectSort(int a[], int n){ int key, tmp; for(int i = 0; i< n; ++i) { key = SelectMinKey(a, n,i); //选择最小的元素 if(key != i){ tmp = a[i]; a[i] = a[key]; a[key] = tmp; //最小元素与第i位置元素互换 } print(a, n , i); }}int main{ int a[8] = {3,1,5,7,2,4,9,6}; cout<<"初始值:"; for(int j= 0; j<8; j++){ cout< } cout< selectSort(a, 8); print(a,8,8);}

void print(int a[], int n){ for(int j= 0; j cout< } cout<}/** * 已知H[s…m]除了H[s] 外均满足堆的定义 * 调整H[s],使其成为大顶堆.即将对第s个结点为根的子树筛选, * * @param H是待调整的堆数组 * @param s是待调整的数组元素的位置 * @param length是数组的长度 */void HeapAdjust(int H[],int s, int length){ int tmp = H[s]; int child = 2*s+1; //左孩子结点的位置。(i+1 为当前调整结点的右孩子结点的位置) while (child < length) { if(child+1 ++child ; } if(H[s] H[s] = H[child]; // 那么把较大的子结点往上移动,替换它的父结点 s = child; // 重新设置s ,即待调整的下一个结点的位置 child = 2*s+1; } else { // 如果当前待调整结点大于它的左右孩子,则不需要调整,直接退出 break; } H[s] = tmp; // 当前待调整的结点放到比其大的孩子结点位置上 } print(H,length);}/** * 初始堆进行调整 * 将H[0..length-1]建成堆 * 调整完之后第一个元素是序列的最小的元素 */void BuildingHeap(int H[], int length){ //最后一个有孩子的节点的位置 i= (length -1) / 2 for (int i = (length -1) / 2 ; i >= 0; --i) HeapAdjust(H,i,length);}/** * 堆排序算法 */void HeapSort(int H[],int length){ //初始堆 BuildingHeap(H, length); //从最后一个元素开始对序列进行调整 for (int i = length - 1; i > 0; --i) { //交换堆顶元素H[0]和堆中最后一个元素 int temp = H[i]; H[i] = H[0]; H[0] = temp; //每次交换堆顶元素和堆中最后一个元素之后,都要对堆进行调整 HeapAdjust(H,0,i); }} int main{ int H[10] = {3,1,5,7,2,4,9,6,10,8}; cout<<"初始值:"; print(H,10); HeapSort(H,10); //selectSort(a, 8); cout<<"结果:"; print(H,10);}
void bubbleSort(int a[], int n){ for(int i =0 ; i< n-1; ++i) { for(int j = 0; j < n-i-1; ++j) { if(a[j] > a[j+1]) { int tmp = a[j] ; a[j] = a[j+1] ; a[j+1] = tmp; } } }}