八大排序算法(C语言)

目录

一.插入排序

1.1直接插入排序

1.2希尔排序

二.选择排序

2.1选择排序

2.2堆排序

三.交换排序

3.1冒泡排序

3.2快速排序

四.归并排序

4.1归并排序

五.非比较排序

5.1计数排序


一.插入排序

基本思想:(默认升序)从数组中的第二个元素开始,先使用一个变量保存这个元素的下标,开然后始跟前一个比较,如果比他小则交换位置继续跟前一个比较,直到找到一个位置比它的前一个元素大则停止(或走到数组开头也停止),然后保存的下标后移继续之前的操作,直到数组末尾.

1.1直接插入排序

基本思想:直接插入排序则是对插入排序思想的直接实现.

时间复杂度:O(N^2)

空间复杂度:O(1)

稳定性:稳定

适用情况:数组越接近有序使用直接插入排序效率越高.

运行图解:

代码:(可直接复制测试)

#include<stdio.h>
#include<stdlib.h>

void InsertSort(int* a,int n){//算法实现
  int i,insert,temp;
  for(i=1;i<n;i++){//从第二个元素开始向前比较
    insert=i-1;//元素要插入的位置
    temp=a[i];//记录当前元素的数值
//开始向前比较,插入位置没到数组头部或insert位置的元素大于要插入元素,一直循环.
    while(insert>=0 && temp<a[insert]){
      a[insert+1]=a[insert];//比插入元素大的直接后移
      insert--;//insert向前
    }
    a[insert+1]=temp;//找到比要插入元素小的元素的位置,直接放到其后面.
  }
}

void PrintList(int* a,int n){//打印数组
  int i;
  for(i=0;i<n;i++){
    printf("%d",a[i]);
  }
  printf("\n");
}

int main(){//测试
  int a[]={3,1,6,2,7,9,8,5,4};
  InsertSort(a,sizeof(a)/sizeof(a[0]));
  PrintList(a,sizeof(a)/sizeof(a[0]));
  return 0;
}

1.2希尔排序

基本思想:对直接插入排序的优化,因为上面我们知道了,直接插入排序在数组接近有序的情况下排序效率越高,所以我们就想办法让数组接近有序,因此我们可以给个间隔,让所有以间隔为一组的数据全部进行直接插入排序,间隔缩小继续进行上面的操作,当间隔为1的时候这时数组已经是接近有序的数组,再次使用直接插入排序效率就会有明显提高.

时间复杂度:O(N^1.25)~O(1.6*N^1.25)之间

空间复杂度:O(1)

稳定性:不稳定

gap(间隔):我们这里的取间隔方式有很多种我在代码中采用gap=n/2,n为数组长度,使gap每次缩小一半直到为1.

适用情况:数组越接近有序效率越高.

运行图解:

 代码:(可直接复制测试)

#include<stdio.h>
#include<stdlib.h>

void ShellSort(int* a,int n){//算法实现
  int i,gap=n/2;
  while(gap>0){//使用gap让数组逐渐接近有序
    i=0;
    //类似直接插入排序(将直接插入排序中的1换为gap)
    while(i+gap<n){
      int insert=i;//元素插入位置
      int temp=a[i+gap];//记录要插入元素的值
//开始向前比较,插入位置没到数组头部或insert位置的元素大于要插入元素,一直循环.
      while(insert>=0 && temp<a[insert]){
        a[insert+gap]=a[insert];//插入位置元素大于要插入的元素,插入位置的元素后移
        insert=insert-gap;//插入位置前移
      }
     a[insert+gap]=temp;//插入
     i++;
    }
    gap=gap/2;//间隔每次缩小一半
  }
}

void PrintArray(int* a,int n){//打印数组
  int i;
  for(i=0;i<n;i++){
    printf("%d",a[i]);
  }
  printf("\n");
}

int main(){//测试
  int a[]={3,4,2,6,5,9,8,7,1};
  ShellSort(a,sizeof(a)/sizeof(a[0]));
  PrintArray(a,sizeof(a)/sizeof(a[0]));
  return 0;
}

二.选择排序

基本思想:(默认升序),从数组所有元素中找出最大的元素放在数组尾,然后再找出第二大的元素放在数组倒数第二的位置,依次类推当进行到最后一个元素时数组刚好是呈升序的有序数组.

2.1选择排序(优化)

基本思想:既然我们每次遍历数组都是找到最大的元素放在数组尾,那么我们可以对该算法进行优化,每次遍历数组不仅找到最大的值也找的最小的值,将最大的值放到数组尾,最小的值放到数组首,这样我们每遍历一次数组就排好了两个值,对比之前的算法提高了接近一倍的效率.

时间复杂度:O(N^2)

空间复杂度:O(1)

稳定性:不稳定

运行图解:

 代码:(可直接复制测试)

#include<stdio.h>
#include<stdlib.h>

void Swap(int* a,int* b){//交换数据
  int temp=*b;
  *b=*a;
  *a=temp;
}

/*
void SelectSort(int* a,int n){//选择排序
  int i,j,max;
  for(i=0;i<n-1;i++){//查找n-1次(最后剩一个元素时不用比较)
    max=0;
    for(j=1;j<n-i;j++){//从第二个元素位置开始向n-i开始遍历寻找最大的值的下标
      if(a[max]<a[j]){
        max=j;
      }
    }
    Swap(&a[max],&a[n-1-i]);//把最大的值与最后一个元素交换
  }
}
*/

void SelectSort1(int* a,int n){//选择排序优化(降序)
  int i,j,max,min,sum=0;
  for(i=0;i<n-1;i=i+2){//每次排好两个元素进行循环
    max=sum;
    min=sum;
    for(j=sum+1;j<n-sum;j++){//从sum+1到n-sum的遍历,查找最大元素下标和最小元素下标
      if(a[max]<a[j]){
        max=j;
      }
      if(a[min]>a[j]){
        min=j;
      }
    }
    Swap(&a[max],&a[sum]);//把最大的元素放到第一位
    if(sum==min){//防止最小的元素本身就在第一位.
      Swap(&a[max],&a[n-1-sum]);
    }else{
      Swap(&a[min],&a[n-1-sum]);
    }
    sum++;
  }
}

void PrintArray(int* a,int n){//打印数组
  int i;
  for(i=0;i<n;i++){
    printf("%d ",a[i]);
  }
  printf("\n");
}

int main(){//测试
  int a[]={3,6,5,2,9,6,8,7,1};
  SelectSort1(a,sizeof(a)/sizeof(a[0]));
  PrintArray(a,sizeof(a)/sizeof(a[0]));
  return 0;
}

2.2堆排序

基本思想:首先我们要了解什么是堆,堆分为大根堆和小根堆,小根堆的双亲结点永远小于其子节点,因此我们可以将数据进行堆调整化成堆,此时的根节点就是最小的元素,我们将此节点从堆中删除,(堆的删除方法就是把根节点与最后一个节点交换,堆的长度减一,然后重新进行堆调整),因此我们一直对堆进行删除操作,当堆中只有一个元素时停止,此时数组正好成降序.使用大根堆则刚好相反.

时间复杂度:O(N*logN)

空间复杂度:O(1)

稳定性:不稳定

运行图解:

代码:(可直接复制测试)

#include<stdio.h>
#include<stdlib.h>

void Swap(int* a,int* b){//交换数据
  int temp=*b;
  *b=*a;
  *a=temp;
}

void AdjustDwon(int* a,int n,int root){//向下调整(堆调整)建立大根堆
  int parents=root;//双亲结点位置
  int child=root*2+1;//孩子结点位置
  while(child<n){//孩子结点位置不能大于数组长度
    if(child+1<n && a[child]<a[child+1]){//在右孩子存在的情况下,让child处在更大的位置
      child++;
    }
    if(a[parents]<a[child]){//如果双亲结点小于孩子结点就进行交换
      Swap(&a[parents],&a[child]);
      parents=child;//交换之后重新设置双亲结点的位置和孩子结点的位置继续进行判断
      child=parents*2+1;
    }else{
//如果双亲结点大于孩子结点就直接退出函数(要进行向下调整的条件就是该节点的左右子树已经是堆了)
      return;
    }
  }
}

void HeapSort(int* a,int n){//堆排序
  int i,j;
  for(i=(n-2)/2;i>=0;i--){//从最后一个双亲结点开始调用向下调整建立堆
    AdjustDwon(a,n,i);
  }
  for(j=n-1;j>0;j--){//使用堆删除进行堆排序
    Swap(&a[0],&a[j]);//将首尾结点数据交换
    AdjustDwon(a,j,0);//将堆的长度减一,从根开始进行向下调整,重新建立堆
  }
}

void PrintArray(int* a,int n){//打印数组
  int i;
  for(i=0;i<n;i++){
    printf("%d ",a[i]);
  }
  printf("\n");
}

int main(){//测试
  int a[]={4,5,2,3,8,9,6,7,1};
  HeapSort(a,sizeof(a)/sizeof(a[0]));
  PrintArray(a,sizeof(a)/sizeof(a[0]));
  return 0;
}

三.交换排序

基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。

3.1冒泡排序

基本思想:(默认升序)令i=0,从第i个元素开始和其相邻的i+1元素数值进行比较,如果i的数值大就进行交换,反之则不用交换,然后i+1,再重复上述操作,直到i走到n-1的位置.这样就将最大的元素放在了最后一位,然后重复上面的操作,已经排序好的则不用再比较,直到数组只剩下一个元素时停止此时数组是成升序的有序数组.

时间复杂度:O(N^2)

空间复杂度:O(1)

稳定性:稳定

运行图解:

代码:(可直接复制测试)

#include<stdio.h>
#include<stdlib.h>

void Swap(int* a,int* b){//交换数据
  int temp=*b;
  *b=*a;
  *a=temp;
}

void BubbleSort(int* a,int n){//冒泡排序
  int i,j,length=n-1;
  for (i=0;i<length;i++){//n个元素总共排序n-1个元素
    int flag=0;
//标志位用来标识一趟循环中是否有数据的交换,如果没有数据的交换说明数组已经为有序数组直接退出函 
//数即可
    for(j=0;j<length-i;j++){//进行一趟循环,每排序好一个元素此循环少进行一次
      if(a[j] < a[j+1]){//(降序)第j个元素与第j+1个元素比较如果比其小就进行交换,并将flag变1
        Swap(&a[j],&a[j+1]);
        flag=1;
      }
    }
    if(0==flag){//没有进行数据交换则说明数组有序,直接退出函数
      break;
    }
  }
}

void PrintArray(int* a,int n){//打印数组
  int i;
  for(i=0;i<n;i++){
    printf("%d ",a[i]);
  }
  printf("\n");
}

int main(){//测试
  int a[]={4,3,2,7,9,8,6,5,1};
  BubbleSort(a,sizeof(a)/sizeof(a[0]));
  PrintArray(a,sizeof(a)/sizeof(a[0]));
  return 0;
}

3.2快速排序

基本思想:(默认升序)从数组中取一个数的来作为标准数,然后遍历数组,将比这个标准数大的放到其后面,比他小的放到其前面,此时这个标准数所在的位置就是他在有序数组中的位置,然后使用递归对其左右的数据也进行这种操作.直到左元素下标大于等于右元素下标时停止,此时数组是成升序的有序数组.

对快速排序的扩展和优化:

a.优化

三数取中:我们取标准数的时候,取的数越居中则算法的时间复杂度越低越接近O(N*logN)效率越高,我们取的数越极端越接近最大或最小值则时间复杂度越高越接近O(N^2)效率越低,因此我们使用三数取中法取得的标准数更居中,具体操作是从数组的首尾中三个位置取三个数,然后以这三个数中居中的数为标准数.

b.扩展(快排的三种方法)

hoare法:先选取标准数,然后定义两个指针(prev(前),rear(后)),一个从前向后走,一个从后向前走,使用prev找到比标准数大的数,使用rear找到比标准数小的数,然后交换两个数,直到prev和rear走到相同位置时退出循环,再将标准数跟此处进行交换即可.

挖坑法:先选取标准数,将标数放到数组尾部然后记录到temp中,然后定义prev(前),rear(后),prev先走找到比标准数大的直接放到数组尾部,然后rear继续走找到比标准数小的直接放到prev的位置,继续重复上面的操作,直到prev和rear走到一起退出循环,将temp中的数放到此处即可.

前后指针法:先选取标准数,将标准数放到数组首部,然后定义前后指针prev在首部,cur=prev+1,两个指针一起向后走,cur找比标准数小的,prev找比标准数大的,都找到后进行交换,当cur走到末尾时退出循环,因为prev所在的位置一直是小于标准数的数中最后一位,所以将标准数与prev所在的位置进行交换,将标准数放在中间即可.

特性:

时间复杂度:O(N*logN)

空间复杂度:O(logN)

稳定性:不稳定

适用情况:数组元素越无序越混乱,该排序效率越高.

运行图解:

hoare:

查看源图像

代码:(可直接复制测试)

#include<stdio.h>
#include<stdlib.h>

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

//取中间数
int MidNum(int* a,int b,int c,int d){
  int n_1=a[b],n_2=a[c],n_3=a[d];
  int temp[3]={n_1,n_2,n_3};
  if(temp[0]>temp[1]){
    Swap(&temp[0],&temp[1]);
  }
  if(temp[1]>temp[2]){
    Swap(&temp[1],&temp[2]);
  }
  if(temp[0]>temp[1]){
    Swap(&temp[0],&temp[1]);
  }
  if(temp[1]==a[b]){
    return b;
  }
  if(temp[1]==a[c]){
    return c;
  }
  return d;
}

//hoare
int PartSort1(int* a,int left,int right){//降序
  int i=left,j=right,mid=MidNum(a,left,(left+right)/2,right);//三数取中
  Swap(&a[right],&a[mid]);//将标准数放到末尾
  int temp=a[right];//记录标准数
  while(i<j){
    while(i<j && a[i]>=temp){//找比标准数小的
      i++;
    }
    while(i<j && a[j]<=temp){//找比标准数大的
      j--;
    }
    Swap(&a[i],&a[j]);//交换
  }
  Swap(&a[i],&a[right]);//将标准数放到中间
  return i;
}

//挖坑法
int PartSort2(int* a,int left,int right){
  int i=left,j=right,mid=MidNum(a,left,(left+right)/2,right);//三数取中
  Swap(&a[mid],&a[right]);//将标准数放到末尾
  int temp=a[right];//记录标准数
  while(i<j){
    while(i<j && a[i]<=temp){//找比标准数大的数
      i++;
    }
    if(i<j){//将其放到j的位置
      a[j]=a[i];
      j--;
    }
    while(i<j && a[j]>=temp){//找比标准数小的数
      j--;
    }
    if(i<j){//将其放到i的位置
      a[i]=a[j];
      i++;
    }
  }
  a[i]=temp;//将标准数的值放到中间
  return i;
}

//前后指针法
int PartSort3(int* a,int left,int right){
  int cur,prev,mid=MidNum(a,left,(left+right)/2,right);//三数取中
  Swap(&a[left],&a[mid]);//将标准数放到首部
  prev=left;//定义前后指针
  cur=prev+1;
  while(cur<=right){
    if(a[cur]<a[left] && ++prev!=cur){//cur找比标准数小的,prev找比标准数大的然后交换
      Swap(&a[cur],&a[prev]);
    }
    cur++;
  }
  Swap(&a[left],&a[prev]);//prev所在的位置永远在小于标准数的最后一位,将标准数与其交换放在中间
  return prev;
}

void QuickSort(int* a,int left,int right){//递归调用
  if(left<right){//当left小于right时一直进行调用
    int mid=PartSort3(a,left,right);//找到标准数所在的位置
    QuickSort(a,left,mid-1);//对标准数的左侧进行调用
    QuickSort(a,mid+1,right);//对标准数的右侧进行调用
  }
}

void PrintArray(int* a,int n){//打印数组
  int i;
  for(i=0;i<n;i++){
    printf("%d ",a[i]);
  }
  printf("\n");
}

int main(){测试
  int a[]={3,4,2,7,5,6,9,8,1};
  QuickSort(a,0,sizeof(a)/sizeof(a[0])-1);
  PrintArray(a,sizeof(a)/sizeof(a[0]));
  return 0;
}

四.归并排序

4.1归并排序

基本思想:(默认升序)先将数组中的元素使用递归进行分解当元素分解成单个数据的时候,采用合并有序数组的方式进行合并.(该算法的核心就是合并两个有序数组),定义三个指针分别指向两个有序数组的首部和一个新的数组的首部(定义一个新的数组用来存储排序好的数据),比较两个有序数组的指针的值将小的放到新的数组中小的这个指针后移,新数组的指针也后移,直到两个有序数组中有一个走到末尾结束循环,将没有走到末尾的那个有序数组中剩余的数据全部放到新数组的后面,此时新数组就是一个成升序的有序数组.

时间复杂度:O(N*logN)

空间复杂度:O(N)

稳定性:稳定

适用范围:归并排序的思考更多的是解决在磁盘中的外排序问题.

运行图解:

代码:(可直接复制测试)

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

//合并两个有序数组(降序)
void MergeOrderArray(int* new_a,int* a,int begin_1,int end_1,int begin_2,int end_2){
  int i=begin_1,j=begin_2,k=begin_1;//定义三个指针
  while(i<=end_1 && j<=end_2){//对两个有序数组进行合并,当其中一个走到末尾时结束循环
    if(a[i] >= a[j]){//比较两个有序数组中的元素,大的那个数放到新的数组中,同时两个指针后移
      new_a[k++]=a[i++];
    }else{
      new_a[k++]=a[j++];
    }
  }
  while(i<=end_1){//如果是以i遍历的数组没有走到末尾,则将其后面的元素全加到新数组的后面
    new_a[k++]=a[i++];
  }
  while(j<=end_2){//如果是以j遍历的数组没有走到末尾,则将其后面的元素全加到新数组的后面
    new_a[k++]=a[j++];
  }
}

void _MergeSort(int* new_a,int* a,int i,int j){//递归调用(类似于二叉树的后序遍历)
  if(i<j){//对数组进行递归分解当i等于j时停止
    int mid=i+((j-i)>>1);//找中间数值
    _MergeSort(new_a,a,i,mid);//分解左边
    _MergeSort(new_a,a,mid+1,j);//分解右边
    MergeOrderArray(new_a,a,i,mid,mid+1,j);//调用合并算法对有序的数组进行合并
    memcpy(a+i,new_a+i,sizeof(int)*(j-i+1));//将合并好的有序数组复制回原数组中
  }
}

void MergeSort(int* a,int n){//归并排序的外部调整
  int* new_a=(int*)malloc(sizeof(int)*n);//创建新数组用来存储有序元素
  if(NULL==new_a){
    return;
  }
  _MergeSort(new_a,a,0,n-1);//递归调用
  free(new_a);
}

void PrintArray(int* a,int n){//打印数组
  int i;
  for(i=0;i<n;i++){
    printf("%d ",a[i]);
  }
  printf("\n");
}

int main(){//测试
  int a[]={3,4,5,2,1,9,8,7,6};
  MergeSort(a,sizeof(a)/sizeof(a[0]));
  PrintArray(a,sizeof(a)/sizeof(a[0]));
  return 0;
}

五.非比较排序

5.1计数排序

基本思想:(默认升序)从一组数据中找出最小和最大的元素,以其差值为界限创建新数组(初始化为0),然后以要排序数组中元素的值为新数组的下标,遍历要排序数组,每遍历到一个数据时在新数组中以该数据为下标的数组内容加一,及统计要排序数组中每个元素出现的次数,最后将这些次数按顺序填入要排序数组中,此时该数组是成升序的有序数组.

时间复杂度:O(max(N,max-min+1))

空间复杂度:O(max-min+1)

稳定性:稳定

使用情况:在元素特别集中时适用.

运行图解:

代码:(可直接复制测试)

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

void CountSort(int* a,int n){//计数排序(降序)
  int max=a[0],min=a[0];
  int i;
  for(i=0;i<n;i++){//获取最大值和最小值
    if(a[i]>max){
      max=a[i];
    }
    if(a[i]<min){
      min=a[i];
    }
  }
  int length=max-min+1;
  int* temp=(int*)calloc(length,sizeof(int));//以max-min+1为界限创建新数组
  for(i=0;i<n;i++){//统计要排序数组中各个元素出现的次数
    temp[a[i]-min]++;
  }
  int k=0;
  for(i=length-1;i>=0;i--){//将统计好的数据依次按顺序填入要排序的数组
    while(temp[i]!=0){
      a[k++]=i+min;
      temp[i]--;
    }
  }
  free(temp);
}

void PrintArray(int* a,int n){//打印数组
  int i;
  for(i=0;i<n;i++){
    printf("%d ",a[i]);
  }
  printf("\n");
}

int main(){//测试
  int a[]={2,3,4,5,6,7,8,2,3,3,2,5,4,7,6,9,8,1};
  CountSort(a,sizeof(a)/sizeof(a[0]));
  PrintArray(a,sizeof(a)/sizeof(a[0]));
  return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_49312527/article/details/123031374