八大排序算法 排序 C语言

前言
不管是提高自身的能力,还是面试,八大排序都是很重要的一个知识点,所以理解并实践实现是很有必要的,以下给出算法思想与代码实现,并且进行运行时间测试八大排序的效率。

时间复杂度对比图
先看下对于算法时间复杂度的对比:

冒泡排序
冒泡排序是一种简单的排序算法。主要思想是顺序的比较相邻的两个数,如果符合比较条件就替换两个数,做法可以从后往前推,也可以从前往后推,每次推出当前长度的最大值,直至最后便可以完整的排序。

时间复杂度为 : O(n*n);

#include<time.h>
#include <stdio.h>
#include <stdlib.h>
#define N 10000
#define MAX 100000000
//冒泡
void bubbleSort(int arr[],int n){
int temp;
for(int i=0;i < n-1;++i){

for(int j=n-1;j>i;--j){
    if(arr[j]<arr[j-1]){
        temp = arr[j];
        arr[j] = arr[j-1];
        arr[j-1] = temp;
    }
 }

}
}

//判断是否排序
bool isSorted(int arr[],int n){
bool flag = true;
for(int i=0;i<n-1;++i){
if(arr[i+1]<arr[i]){
flag = false;
break;
}
}
return flag;
}

int arr[N];

int main()
{
//随机播种
srand((unsigned)time(NULL));
for(int i =0;i < N;++i ){
arr[i] = rand()% MAX;
}

clock_t start_time=clock();
{
    bubbleSort(arr,N);
}

clock_t end_time=clock();
printf(“Running time is: %lfms\n”,static_cast(end_time-start_time)/CLOCKS_PER_SEC*1000);
printf("%s",isSorted(arr,N) == true ? “true”:“false”);
printf("\n");
return 0;
}

用10000个数进行测试:

运行时间为279ms
不要觉得很快这才一万的数据,再加两个0,就听几首歌慢慢等吧
冒泡有改进的方法是如果在比较相邻数的过程中都没交换过一次,说明后面的数已经排好序,可以直接跳出循环。

选择排序
思想:设当前位置为i,每次从i+1到n的数中选择最大(从大到小)或最小(从小到大)后,与位置i的值进行条件比较,符合条件则调换。

**时间复杂度为:**O(n*n)

#include<time.h>
#include <stdio.h>
#include <stdlib.h>
#define N 10000
#define MAX 100000000

//选择排序
void chioseSort(int arr[],int n){
int temp,index;
for(int i=0;i < n-1;++i){
index = i;
for(int j=i+1;j<n;++j){
if(arr[j]<arr[index])
index = j;
}
if(index!=i){
temp = arr[i];
arr[i] = arr[index];
arr[index] = temp;
}
}
}

//判断是否排序
bool isSorted(int arr[],int n){
bool flag = true;
for(int i=0;i<n-1;++i){
if(arr[i+1]<arr[i]){
flag = false;
break;
}
}
return flag;
}

int arr[N];

int main()
{
srand((unsigned)time(NULL));
for(int i =0;i < N;++i ){
arr[i] = rand()% MAX;
}

clock_t start_time=clock();
{

    chioseSort(arr,N);
}

clock_t end_time=clock();
printf(“Running time is: %lfms\n”,static_cast(end_time-start_time)/CLOCKS_PER_SEC*1000);
printf("%s",isSorted(arr,N) == true ? “true”:“false”);
printf("\n");
return 0;
}

用10000个数进行测试:

运行时间为141ms
可以发现比冒泡速度更快点,主要原因是选择不需要每次都进行替换,只在最后一次再对比替换。

直接插入排序
思想:设当前位置为i,记录当前i的值为value,每次从i到0推,在推的过程中比较value的值,符合比较条件的值向后移,直至不符合条件时推结束,然后再将之前记录的value赋给不符和条件索引下标的后一个。
动画演示:

**时间复杂度:**O(n*n)

#include<time.h>
#include <stdio.h>
#include <stdlib.h>
#define N 10000
#define MAX 100000000
//插入排序
void insertSort(int arr[], int len)
{
int i, j, key;
for(i = 1; i < len; ++i){
key = arr[i];
for(j = i-1; j >=0; --j){
if(arr[j] > key)
arr[j+1] = arr[j];
else
break;
}
arr[j+1] = key;
}
}

//判断是否排序
bool isSorted(int arr[],int n){
bool flag = true;
for(int i=0;i<n-1;++i){
if(arr[i+1]<arr[i]){
flag = false;
break;
}
}
return flag;
}

int arr[N];

int main()
{
srand((unsigned)time(NULL));
for(int i =0;i < N;++i ){
arr[i] = rand()% MAX;
}

clock_t start_time=clock();
{
  //  bubbleSort(arr,N);
   // chioseSort(arr,N);
    insertSort(arr,N);
}

clock_t end_time=clock();
printf(“Running time is: %lfms\n”,static_cast(end_time-start_time)/CLOCKS_PER_SEC*1000);
printf("%s",isSorted(arr,N) == true ? “true”:“false”);
printf("\n");
return 0;
}

用10000个数进行测试:

用时75ms
可以发现比以上两种都快,原因主要是插入排序没有用到交换,而是直接赋值,交换两个数要3步,直接赋值只要1步,在数据的大情况下就差了2倍。

快速排序
思想:将一个复杂的问题划分为两部分,递归。每次在当前部分的数组中任取一个值value,重排数组(从小到大来说)将比value小的放左边,比value大的放右边,递归划分后得到的就是一个排完序的。

图片演示:

时间复杂度:O(nlogn);
特殊情况下(已经排好序的):O(n
n);

#include<time.h>
#include <stdio.h>
#include <stdlib.h>
#define N 10000
#define MAX 100000000
void swap(int *a,int *b){
int temp = *a;
*a = *b;
*b = temp;
}

int partition(int arr[], int start, int end){

swap(&arr[start],&arr[end]);
int value = arr[end];
int index = start;
for(int i=start;i<end;++i){
    if(arr[i]< value){
        swap(&arr[index++],&arr[i]);
    }
}
swap(&arr[index],&arr[end]);
return index;

}

void quickSort(int arr[],int start,int end){

if(end>start){
    int index = partition(arr,start,end);
    quickSort(arr, start,index-1);
    quickSort(arr, index+1,end);
}

}

//判断是否排序
bool isSorted(int arr[],int n){
bool flag = true;
for(int i=0;i<n-1;++i){
if(arr[i+1]<arr[i]){
flag = false;
break;
}
}
return flag;
}

int arr[N];

int main()
{
srand((unsigned)time(NULL));
for(int i =0;i < N;++i ){
arr[i] = rand()% MAX;
}

clock_t start_time=clock();
{
    quickSort(arr,0,N-1);
}

clock_t end_time=clock();
printf(“Running time is: %lfms\n”,static_cast(end_time-start_time)/CLOCKS_PER_SEC*1000);
printf("%s",isSorted(arr,N) == true ? “true”:“false”);
printf("\n");
// for(int i=0;i<N;++i){
// printf("%d ",arr[i]);
// }
return 0;
}

同样10000数据
只用了1ms
可知与以上的方法相比快了很多很多。

堆排序
二叉堆的定义

二叉堆是完全二叉树或者是近似完全二叉树。
二叉堆满足二个特性:
1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。
当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆。当父结点的键值总是小于或等于任何一个子节点的键值时为最小堆。下图展示一个最小堆:

堆的存储
一般都用数组来表示堆,i结点的父结点下标就为(i – 1) / 2。它的左右子结点下标分别为2 * i + 1和2 * i + 2。如第0个结点左右子结点下标分别为1和2。

堆的操作——插入删除

堆的插入
每次插入都是将新数据放在数组最后。可以发现从这个新数据的父结点到根结点必然为一个有序的数列

// 新加入i结点 其父结点为(i - 1) / 2
void MinHeapFixup(int a[], int i)
{
int j, temp;

temp = a[i];  
j = (i - 1) / 2;      //父结点  
while (j >= 0 && i != 0)  
{  
    if (a[j] <= temp)  
        break;  

    a[i] = a[j];     //把较大的子结点往下移动,替换它的子结点  
    i = j;  
    j = (i - 1) / 2;  
}  
a[i] = temp;  

}

更简短的表达为:

void MinHeapFixup(int a[], int i)
{
for (int j = (i - 1) / 2; (j >= 0 && i != 0)&& a[i] > a[j]; i = j, j = (i - 1) / 2)
Swap(a[i], a[j]);
}
1
2
3
4
5
堆的删除
按定义,堆中每次都只能删除第0个数据。为了便于重建堆,实际的操作是将最后一个数据的值赋给根结点,然后再从根结点开始进行一次从上向下的调整。调整时先在左右儿子结点中找最小的,如果父结点比这个最小的子结点还小说明不需要调整了,反之将父结点和它交换后再考虑后面的结点。相当于从根结点将一个数据的“下沉”过程。下面给出代码:

// 从i节点开始调整,n为节点总数 从0开始计算 i节点的子节点为 2i+1, 2i+2
void MinHeapFixdown(int a[], int i, int n)
{
int j, temp;

temp = a[i];  
j = 2 * i + 1;  
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;  

}
//在最小堆中删除数
void MinHeapDeleteNumber(int a[], int n)
{
Swap(a[0], a[n - 1]);
MinHeapFixdown(a, 0, n - 1);
}

堆排序的两大步骤:

  1. 建立初始化堆
  2. 每次选取堆定元素进行排序
    建立初始化堆
    有了堆的插入和删除后,再考虑下如何对一个数据进行堆化操作。要一个一个的从数组中取出数据来建立堆吧,不用!先看一个数组,如下图:

很明显,对叶子结点来说,可以认为它已经是一个合法的堆了即20,60, 65, 4, 49都分别是一个合法的堆。只要从A[4]=50开始向下调整就可以了。然后再取A[3]=30,A[2] = 17,A[1] = 12,A[0] = 9分别作一次向下调整操作就可以了。下图展示了这些步骤:

写出堆化数组的代码:

// 从i节点开始调整,n为节点总数 从0开始计算 i节点的子节点为 2i+1, 2i+2
void MinHeapFixdown(int a[], int i, int n)
{
int j, temp;

temp = a[i];  
j = 2 * i + 1;  
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;  

}
//建立最小堆
void MakeMinHeap(int a[], int n)
{
for (int i = n/ 2 -1 ; i >= 0; i–)
MinHeapFixdown(a, i, n);
}

每次选取堆定元素进行排序
首先可以看到堆建好之后堆中第0个数据是堆中最小的数据。取出这个数据再执行下堆的删除操作。这样堆中第0个数据又是堆中最小的数据,重复上述步骤直至堆中只有一个数据时就直接取出这个数据。

由于堆也是用数组模拟的,故堆化数组后,第一次将A[0]与A[n - 1]交换,再对A[0…n-2]重新恢复堆。第二次将A[0]与A[n – 2]交换,再对A[0…n - 3]重新恢复堆,重复这样的操作直到A[0]与A[1]交换。由于每次都是将最小的数据并入到后面的有序区间,故操作完成后整个数组就有序了。

void MinheapsortTodescendarray(int a[], int n)
{
for (int i = n - 1; i >= 1; i–)
{
Swap(a[i], a[0]);
MinHeapFixdown(a, 0, i);
}
}

注意使用最小堆排序后是递减数组,要得到递增数组,可以使用最大堆。

时间复杂度为:O(n*logn)

#include<time.h>
#include <stdio.h>
#include <stdlib.h>
#define N 10000000
#define MAX 100000000

void swap(int *a,int *b){
int temp = *a;
*a = *b;
*b = temp;
}

//判断是否排序
bool isSorted(int arr[],int n){
bool flag = true;
for(int i=0;i<n-1;++i){
if(arr[i+1]<arr[i]){
flag = false;
break;
}
}
return flag;
}

//----------------

//堆排序
//调整堆
void adjustHeap(int arr[],int index,int lenght){

int l = index*2+1;
int n = lenght;
int temp = arr[index];
while(l<n){

    if((l+1)<n&&arr[l+1]>arr[l])
        l++;
    if(arr[l] <= temp)
        break;
    arr[index] = arr[l];
    index = l;
    l = index*2+1;
}

arr[index] = temp;

}

//新建堆
void buildHeap(int arr[],int n){

for(int i = n/2 -1;i>=0;--i){
    adjustHeap(arr,i,n);
}

}

void heapSort(int arr[],int n){

buildHeap(arr,n);

for(int i = n-1;i>0;--i){
    swap(&arr[0],&arr[i]);
    adjustHeap(arr,0,i);
}

}

//----------------------

int arr[N];

int main()
{
srand((unsigned)time(NULL));
for(int i =0;i < N;++i ){
arr[i] = rand()% MAX;
}

clock_t start_time=clock();
{
  //  bubbleSort(arr,N);
   // chioseSort(arr,N);
    //insertSort(arr,N);
   // quickSort(arr,0,N-1);
    heapSort(arr,N);
}

clock_t end_time=clock();
printf(“Running time is: %lfms\n”,static_cast(end_time-start_time)/CLOCKS_PER_SEC*1000);
printf("%s",isSorted(arr,N) == true ? “true”:“false”);
printf("\n");
// for(int i=0;i<N;++i){
// printf("%d ",arr[i]);
// }
return 0;
}

排序1000万数据:

运行速度为4096ms

用快排排序1000万数据:

运行速度为6381ms

可知在数据特别大的情况下堆排序的效率会比快排更好

希尔排序
略,笔者暂时没有研究希尔排序

归并排序
思想:所谓归并就是用了分治的算法思想(将一个大的问题划分为n个小问题,再用小问题得出的结果,解决大问题),主要思想就是将两个已经排好序的数组合并,最后这个数组就是有序的。
主要有两大步骤:

  1. 先递归分解
  2. 再递归合并
    分解
    一般采取二分的方法:将一个数组分为两部分,自顶向下分解

合并:
当全都分解完后,开始递归合并,自底向上合并

所以先了解下合并两数组:
将两个已经排好序的合并成一个排序数组

//辅助数组
int temp[N];
//-----------------------
void combineArr(int arrL[],int ln,int arrR[],int rn){

int i = 0;
int j = 0;
int k=0;
while(i<ln&&j<rn){

    if(arrL[i]<=arrR[j]){
        temp[k++] = arrL[i++];
    }else{
        temp[k++] = arrR[j++];
    }
}
while(i<ln){
    temp[k++] = arrL[i++];
}

while(j<rn){
    temp[k++] = arrR[j++];
}

for(int i = 0;i<k;++i){
    arrL[i] = temp[i];
}

}

完整代码:

#include<time.h>
#include <stdio.h>
#include <stdlib.h>
#define N 10000000
#define MAX 100000000

//辅助数组
int temp[N];
//-----------------------
void combineArr(int arrL[],int ln,int arrR[],int rn){

int i = 0;
int j = 0;
int k=0;
while(i<ln&&j<rn){

    if(arrL[i]<=arrR[j]){
        temp[k++] = arrL[i++];
    }else{
        temp[k++] = arrR[j++];
    }
}
while(i<ln){
    temp[k++] = arrL[i++];
}

while(j<rn){
    temp[k++] = arrR[j++];
}

for(int i = 0;i<k;++i){
    arrL[i] = temp[i];
}

}

void conflationSort(int arr[],int l,int r){

if(r>l){
    int mid = (l+r)/2;
    conflationSort(arr,l,mid);
    conflationSort(arr,mid+1,r);
    combineArr(&arr[l],mid-l+1,&arr[mid+1],r-mid);
}

}

//----------------------

int arr[N];

int main()
{
srand((unsigned)time(NULL));
for(int i =0;i < N;++i ){
arr[i] = rand()% MAX;
}

clock_t start_time=clock();
{
  //  bubbleSort(arr,N);
   // chioseSort(arr,N);
    //insertSort(arr,N);
 //   quickSort(arr,0,N-1);
    //heapSort(arr,N);
    conflationSort(arr,0,N-1);
}

clock_t end_time=clock();
printf(“Running time is: %lfms\n”,static_cast(end_time-start_time)/CLOCKS_PER_SEC*1000);
printf("%s",isSorted(arr,N) == true ? “true”:“false”);
printf("\n");
// for(int i=0;i<N;++i){
// printf("%d ",arr[i]);
// }
return 0;
}

1000万数据,运行时速度为2642ms

时间复杂度为:O(n*logn)

可知归并排序比快排,堆排序都快,并且稳定。

基数排序(桶排序)
基数排序是一种特殊的排序,它并不是通过比较数组中的数值进行排序,而是用了很特殊的方法:通过一个二维数组,每次根据进行位数入桶,最后按顺序遍历这二维数组,便可得出一个排好序的数组。

口诉太抽象了,下面用图解释:

时间近似复杂度为:O(n)

#include<time.h>
#include <stdio.h>
#include <stdlib.h>
#define N 10000000
#define MAX 100000000

//------------基数排序
//辅助数组
int temps[10][N];
//获取最大位数的位置 如8->1 , 17->2,7832->4
int getMexExp(int arr[],int n){

int maxN = arr[0];
for(int i=1;i<n;++i){
    maxN = arr[i]>maxN?arr[i]:maxN;
}
int exps = 1;
maxN = maxN/10;
while(maxN!=0){
    maxN = maxN/10;
    exps++;
}
return exps;

}
//基数排序
void radixSort(int arr[],int n){
//存每个桶中数的个数
int countIndex[10] = {0};
int exps = getMexExp(arr,n);
int exp = 1;
for(int e=0;e<exps;++e){
//入桶
for(int i=0;i<n;++i){
int index = (arr[i]/exp)%10;
temps[index][countIndex[index]++] = arr[i];
}

    //取值,改变数组
    int k=0;
    for(int i=0;i<10;++i){
        //如果桶中数的个数大于0
        if(countIndex[i]>0){
            int n  = countIndex[i];
            //将数取出,改变数组
            for(int j =0;j<n;++j){
                arr[k++] = temps[i][j];
            }
        }

    }
    //将桶中的数的个数重置为0
    for(int i=0;i<10;++i)
        countIndex[i] = 0;
    //位数改变
    exp *=10;
}

}

//--------------------

int arr[N];

int main()
{
srand((unsigned)time(NULL));
for(int i =0;i < N;++i ){
arr[i] = rand()% MAX;
}

clock_t start_time=clock();
{
  //  bubbleSort(arr,N);
   // chioseSort(arr,N);
    //insertSort(arr,N);
 //   quickSort(arr,0,N-1);
    //heapSort(arr,N);
   // conflationSort(arr,0,N-1);
    radixSort(arr,N);
}

clock_t end_time=clock();
printf(“Running time is: %lfms\n”,static_cast(end_time-start_time)/CLOCKS_PER_SEC*1000);
printf("%s",isSorted(arr,N) == true ? “true”:“false”);
printf("\n");
// for(int i=0;i<N;++i){
// printf("%d ",arr[i]);
// }
return 0;
}

1000万数据,运行用时只用了941ms,
基数排序会占用比较大的空间,网上还有空间压缩的方法可参照:http://www.cnblogs.com/skywang12345/p/3603669.html

可知:基数排序的效率比以上所有排序速度都快(在数特别多的情况下,如果位数k,桶数10,k*10>n(数组个数),就不用基数排序了)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值