前言
排序是《数据结构》中最基本的学习内容。排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序。而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。知识框架如下:
插入排序
插入排序是一种简单直观的排序方法,其基本思想在于每次将一个待排序记录,按其关键字大小插入到前面已经排好序的子序列中,直到全部记录插入完成。
直接插入排序
直接插入排序把要排序的序列分成两部分:第一部分是有序子序列,而第二部分是无序序列。每次挑选无序序列中的第一个元素与第一部分的有序子序列从后往前逐个比较,当待排元素大于子序列中的元素时,将其插入到该元素的后方。直接插入排序会使用一个"哨兵",用于存放元素。直接插入排序每次插入一个元素,所以排序趟数固定为n-1。
空间复杂度:O(1) 时间复杂度:O(n^2) 稳定性:稳定
#include <stdio.h>
void insertsort(int a[],int n)
{
int i,j,k;
for(i=2;i<n;i++){
a[0]=a[i];
for(j=i-1;j>0;j--){
if(a[0]>a[j]){
break;
}
else {
a[j+1]=a[j];
}
}
a[j+1]=a[0];
}
}
int main()
{
int i,a[11];
printf("输入10个数:");
for(i=1;i<11;i++)
scanf("%d",&a[i]);
insertsort(a,11);
printf("排序结果是:");
for(i=1;i<11;i++)
printf("%d ",a[i]);
printf("\n");
}
折半插入排序
折半插入排序仅仅是减少了比较元素的次数,约为O(nlogn),该比较次数与待排序列的初始状态无关,仅取决于序列中的元素个数n;而元素的移动次数没有改变,它依赖于待排序列的初始状态。因此,折半插入排序的时间复杂度仍是O(n^2)。
空间复杂度:O(1) 时间复杂度:O(n^2) 稳定性:稳定
#include <stdio.h>
void BinaryInsertSort(int *arry, int n)
{
int i,j;
int high,low,mid;
int temp;
for(i = 2; i < n; i++)
{
arry[0] = arry[i];
low = 1;
high = i-1;
while (low <= high)
{
mid = (low+high)/2;
if (arry[mid] > arry[0])
{
high = mid-1;
}
else if (arry[mid] <= arry[0])
{
low = mid+1;
}
}
for(j = i-1; j >= low; j--)
{
arry[j+1] = arry[j];
}
arry[low] = arry[0];
}
}
int main()
{
int a[] = {0,2,45,7,8,45,3,6,0};
int iLen =sizeof(a)/sizeof(a[0]);
for(int i = 1; i < iLen; i++)
{
printf("%d ", a[i]);
}
printf("\n");
BinaryInsertSort(a, iLen);
for(int i = 1; i < iLen; i++)
{
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
希尔排序
希尔排序是插入排序的一种又称“缩小增量排序”,是直接插入排序算法的一种更高效的改进版本。基本思想:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量 = 1(<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。空间复杂度:O(1) 时间复杂度:O(n^2) 稳定性:不稳定
#include <stdio.h>
void ShellInsertSort(int *arry, int n, int d)
{
int i,j;
for(i = d+1; i < n; i++)
{
if (arry[i-d] > arry[i])
{
arry[0] = arry[i];
for(j = i-d; j > 0 && arry[0] < arry[j]; j = j-d)
{
arry[j+d] = arry[j];
}
arry[j+d] = arry[0];
}
}
}
void ShellSort(int *arry, int n, int d)
{
int i;
for(i = d; i > 0; i--)
{
ShellInsertSort(arry, n, i);
}
}
int main()
{
int a[] = {0,2,4,7,8,1,3,6,0};
int iLen = sizeof(a)/sizeof(a[0]);
for(int i = 1; i < iLen; i++)
{
printf("%d ", a[i]);
}
printf("\n");
ShellSort(a, iLen, 4);
for(int i = 1; i < iLen; i++)
{
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
交换排序
交换排序就是根据序列中两个元素关键字的比较结果来对换这两个记录在序列中的位置。常用的交换排序算法有冒泡和快排。交换类的排序,其趟数和原始序列状态有关。
冒泡排序
冒泡排序算法的基本思想是:假设待排序列长为n,从前往后比较相邻元素的值,若为逆序,则交换它们,直到序列比较完。每趟冒泡至少把序列中的一个元素放到序列的最终位置,且最多做n-1趟冒泡就可以把所有元素排好序。注意:冒泡排序中所产生的有序子序列一定是全局有序(不同于直接插入排序),也就是说有序子序列中的所有元素的关键字一定小于或大于无序序列中所有元素的关键字,这样每一趟排序都会将一个元素放置到其最终的位置上。
空间复杂度:O(1) 时间复杂度:O(n^2) 稳定性:稳定
#include <stdio.h>
void bubblesort1(int *arry,int n)
{
int i,j,k;
int temp,flag;
for(i=0;i<n;i++)
{
flag=0;
for(j=0;j<n-i-1;j++)
{
if(arry[j]>arry[j+1])
{
temp=arry[j];
arry[j]=arry[j+1];
arry[j+1]=temp;
flag=1;
}
}
if(flag==0) break;
}
}
void bubblesort2(int a[], int n) //双向冒泡排序
{
int low = 0,high = n-1;
int i,t,flag = 1;
while (low < high && flag)
{
flag = 0;
for(i = low;i < high; i++)
{
if (a[i] > a[i+1])
{
t = a[i];
a[i] = a[i+1];
a[i+1] = t;
flag = 1;
}
}
high--;
for(i = high;i > low; i--)
{
if (a[i] < a[i-1])
{
t = a[i-1];
a[i-1] = a[i];
a[i] = t;
flag = 1;
}
}
low++;
}
}
int main()
{
int a[10]={5,4,8,7,9,5,4,6,3,2};
int i;
for(i=0;i<10;i++)
printf("%d ",a[i]);
bubblesort1(a,10);
printf("\n");
for(i=0;i<10;i++)
printf("%d ",a[i]);
printf("\n");
return 0;
}
快速排序
快排算法是基于分治策略的排序算法,其基本思想是,对于输入的数组a[low, high],按以下三个步骤进行排序。
(1)分解:以a[p]为基准将a[low: high]划分为三段a[low:p-1],a[p]和a[p+1:high],使得a[low:p-1]中任何一个元素小于等于a[p], 而a[p+1: high]中任何一个元素大于等于a[p]。
(2)递归求解:通过递归调用快速排序算法分别对a[low:p-1]和a[p+1:high]进行排序。
(3)合并:由于对a[low:p-1]和a[p+1:high]的排序是就地进行的,所以在a[low:p-1]和a[p+1:high]都已排好序后,不需要执行任何计算,a[low:high]就已经排好序了。
空间复杂度:O(logn) 时间复杂度:O(nlogn) 稳定性:不稳定
#include <iostream>
using namespace std;
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);
}
int main()
{
int a[] = {57, 68, 59, 52, 72, 28, 96, 33, 24};
Qsort(a, 0, sizeof(a) / sizeof(a[0]) - 1);/*这里原文第三个参数要减1否则内存越界*/
for(int i = 0; i < sizeof(a) / sizeof(a[0]); i++){
cout << a[i] << "";
}
return 0;
}
选择排序
选择排序是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。
简单选择排序
空间复杂度:O(1) 时间复杂度:O(n^2) 稳定性:不稳定
#include <stdio.h>
void selectionsort(int a[],int n)
{
int i,j,k,t;
for(i=0;i<n-1;i++)
{
k=i;
for(j=i+1;j<n;j++)
{
if(a[j]<a[k])
k=j;
}
if(k!=i)
{
t=a[i];
a[i]=a[k];
a[k]=t;
}
}
}
void main()
{
int i,a[10];
for(i=0;i<10;i++)
scanf("%d",&a[i]);
selectionsort(a,10);
for(i=0;i<10;i++)
printf("%d ",a[i]);
printf("\n");
}
堆排序
堆排序是指利用堆这种数据结构所设计的一种排序算法。分为两种方法:大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;
空间复杂度:O(1) 时间复杂度:O(nlogn) 稳定性:不稳定
#include <stdio.h>
#include <stdlib.h>
void swap(int* a, int* b)
{
int temp = *b;
*b = *a;
*a = temp;
}
void max_heapify(int arr[], int start, int end)
{
//建立父节点指标和子节点指标
int dad = start;
int son = dad * 2 + 1;
while (son <= end) //若子节点指标在范围内才做比较
{
if (son + 1 <= end && arr[son] < arr[son + 1])
//先比较两个子节点大小,选择最大的
son++;
if (arr[dad] > arr[son]) //如果父节点大於子节点代表调整完毕,直接跳出函数
return;
else //否则交换父子内容再继续子节点和孙节点比较
{
swap(&arr[dad], &arr[son]);
dad = son;
son = dad * 2 + 1;
}
}
}
void heap_sort(int arr[], int len)
{
int i;
//初始化,i从最後一个父节点开始调整
for (i = len / 2 - 1; i >= 0; i--)
max_heapify(arr, i, len - 1);
//先将第一个元素和已排好元素前一位做交换,再重新调整,直到排序完毕
for (i = len - 1; i > 0; i--)
{
swap(&arr[0], &arr[i]);
max_heapify(arr, 0, i - 1);
}
}
int main() {
int arr[] = { 3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6 };
int len = (int) sizeof(arr) / sizeof(*arr);
heap_sort(arr, len);
int i;
for (i = 0; i < len; i++)
printf("%d ", arr[i]);
printf("\n");
return 0;
}
归并排序
归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。归并排序是一种稳定的排序方法。注意:一般而言,对于N个元素进行k-路归并排序时,排序的趟数m满足k^m = N,从而m = logk(N)向上取整。
空间复杂度:O(n) 时间复杂度:O(nlogn) 稳定性:稳定
#include <stdlib.h>
#include <stdio.h>
void Merge(int sourceArr[],int tempArr[], int startIndex, int midIndex, int endIndex)
{
int i = startIndex, j=midIndex+1, k = startIndex;
while(i!=midIndex+1 && j!=endIndex+1)
{
if(sourceArr[i] > sourceArr[j])
tempArr[k++] = sourceArr[j++];
else
tempArr[k++] = sourceArr[i++];
}
while(i != midIndex+1)
tempArr[k++] = sourceArr[i++];
while(j != endIndex+1)
tempArr[k++] = sourceArr[j++];
for(i=startIndex; i<=endIndex; i++)
sourceArr[i] = tempArr[i];
}
//内部使用递归
void MergeSort(int sourceArr[], int tempArr[], int startIndex, int endIndex)
{
int midIndex;
if(startIndex < endIndex)
{
midIndex = startIndex + (endIndex-startIndex) / 2;//避免溢出int
MergeSort(sourceArr, tempArr, startIndex, midIndex);
MergeSort(sourceArr, tempArr, midIndex+1, endIndex);
Merge(sourceArr, tempArr, startIndex, midIndex, endIndex);
}
}
int main(int argc, char * argv[])
{
int a[8] = {50, 10, 20, 30, 70, 40, 80, 60};
int i, b[8];
MergeSort(a, b, 0, 7);
for(i=0; i<8; i++)
printf("%d ", a[i]);
printf("\n");
return 0;
}
常用排序算法复杂度和稳定性总结
动态图片来源于: