八大排序算法详细总结

一、排序算法说明   

  排序 的目的是将一段无序的记录序列调整为一段有序的记录序列。排序分为内部排序和外部排序两种。

             内部排序:整个排序过程都在内存中完成,不需要访问外存。

             外部排序:因需要排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。

常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。

稳定性:是指假设在某一序列中存在两个或两个以上的记录具有相同的关键字,在用某种排序法排序后,若这些相同关键字的元素的相对次序任然不变,则这种排序方法是稳定的。其中冒泡,插入,基数,归并属于稳定排序,选择,快速,希尔,归属于不稳定排序。

目录

一、排序算法说明   

二、排序算法详细介绍

1、冒泡排序

2、选择排序

3、插入排序

4、希尔排序

5、归并排序

6、快速排序

7、堆排序

8、计数排序


二、排序算法详细介绍

1、冒泡排序

冒泡排序的核心思想:每次比较相邻的两个元素,如果它们的顺序错误就把他们互换

举个栗子:比如我们想要将 10,9,22,15 这四个数从打到小排序,那么我们使用冒泡排序的排序过程就是,首先比较10和9,(9<10)不用互换,第一次比较后,序列变成(10,9,22,15),接着比较9和22,(9<22)那么9和22互换,第二次比较后序列变成(10,22,9,15),接着比较9和15,(9<15)那么9和15互换,第三次比较后序列变成(10,22,15,9)。经过这三次比较后,我们发现最小的已经归为了,这样我们称为“一趟”。接着我们进行第二趟,将第二小的数归为,首先我们比较10和22,(10<22)那么10和22互换,变成(22,10,15,9),接着10和15比较,(10<15)那么10和15互换,变成(22,15,10,9)因为我们在第一趟的时候已经计算出9是最小的了,所以10和9就不用再比较了。第二趟结束后此时的序列是(22,15,10,9),可能你发现这就排好了,不用在排了,其实计算机不知道有没有排好,它会接着第三趟,22与15比较,不用互换,然后排序完成,程序结束。这算是一次完整的冒泡排序过程。

#include<stdio.h>
int main()
{
	int a[10]={0};
	int i,j;
	int temp;
	int n;
	scanf("%d",&n); //输入n个数
	for(i=0;i<n;i++)
	{
		scanf("%d",&a[i]);
	} 
	//冒泡排序核心部分 
	for(i=0;i<n-1;i++)//n个数排序,只需n-1趟 
	{
		for(j=0;j<n-i;j++)
		{
			if(a[j]<a[j+1])//根据要求比较大小并互换 
			{
				temp=a[j+1];
				a[j+1]=a[j];
				a[j]=temp;
			}
		}
	}
	for(i=0;i<n;i++)
	    printf("%d ",a[i]);
	return 0;
} 

当然你还可以对冒泡排序进行优化,就比如上面那个例子,第三趟可以不再进行,(其实这里的第三趟还是会进行,如果它还有第四趟,优化后,就不能进行了),优化很简单,添加一个标志就可以了。

#include<stdio.h>
int main()
{
	int a[10]={0};
	int i,j;
	int temp;
	int n;
	scanf("%d",&n); 
	for(i=0;i<n;i++)
	{
		scanf("%d",&a[i]);
	} 
	for(i=0;i<n-1;i++)
	{
		int flag =1; //此处定义一个标志
		for(j=0;j<n-i;j++)
		{
			if(a[j]<a[j+1])//如果没排好,还会进入
			{
				temp=a[j+1];
				a[j+1]=a[j];
				a[j]=temp;
				
				flag=0;//标志会发生变化
			}
		}
		if(flag =1)//如果未发生变化,表示序列已经排好。
		break;
	}
	for(i=0;i<4;i++)
	    printf("%d ",a[i]);
	return 0;
} 

2、选择排序

选择排序的思路是:首先,找到数组中最小(大)的元素,拎出来,将它和数组的第一个元素交换位置,第二步,在剩下的元素中继续寻找最小(大)的元素,拎出来,和数组的第二个元素交换位置,如此循环,直到整个数组排序完成。

选择排序是一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间。

#include<stdio.h>
int main()
{
	int n,i,j,temp,a[10]={0};
	scanf("%d",&n);
	for(i=0;i<n;i++)
		scanf("%d",&a[i]);
	for(i=0;i<n-1;i++)
	{
		int min = i;
		for(j=i+1;j<n;j++)//遍历未排序的元素 
		{
			if(a[j]<a[min])//找到目前最小值 
			    min=j;//记录最小值 
		}
		temp=a[min];//交换 
		a[min]=a[i];
		a[i]=temp;
	}
	for(i=0;i<n;i++)
		printf("%d ",a[i]);
	return 0;
}
 
//输入:4 
       10 9 22 15
//输出:9 10 15 22

3、插入排序

插入排序的思路是:首先,将未排序的序列的第一个元素看作一个有序序列,把从第二个元素开始直到最后一个元素看成未排序序列。然后,从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)

#include<stdio.h>
int main()
{
	int n,i,j,k,temp,a[10]={0};
	scanf("%d",&n);
	for(i=0;i<n;i++)
		scanf("%d",&a[i]);
	for(i=1;i<n;i++)//默认第一个元素(下标为0)为有序的,所以从第二个元素(下标为1)开始 
	{
		temp=a[i];//将未排序元素给temp 	
		for(j=i-1;j>=0;j--) //在有序序列最右边开始比较,寻找比temp小的数 
		{	
			if(a[j]>temp)
		    	     a[j+1]=a[j];//此处要注意理解 
                        else
                             break;
		}
		a[j+1]=temp;
	}
	for(i=0;i<n;i++)
		printf("%d ",a[i]);
	return 0;
} 

4、希尔排序

希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

  • 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
  • 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;

希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。

#include<stdio.h>
void shell_sort(int a[],int len)
{
	int gap,i,j;
	int temp;
	for(gap=len/2;gap>0;gap=gap/2)//可能你会问为什么区间要以gap=gap/2去计算,其实最优的区间计算方法是没有答案的,不过差不多都会取在二分之一到三分之一附近
	{
		for(i=gap;i<len;i++)
		{
			temp=a[i];
			for(j=i-gap;j>=0;j=j-gap)//可以发现,当区间为1的时候,它使用的排序方式就是插入排序。
			{
				if(a[j]>temp)
				    a[j+gap]=a[j]; 
				else
				    break;
			}
			a[j+gap]=temp;
		}
	}
}
int main()
{
	int i,j,k,n,a[10]={0};
	scanf("%d",&n);
	for(i=0;i<n;i++)
		scanf("%d",&a[i]); 
	shell_sort(a,n);
	for(i=0;i<n;i++)
		printf("%d ",a[i]);
	return 0; 
} 

5、归并排序

在我们要了解归并排序,以及接下来的快速排序之前,我们必须了解什么是分治算法。他们都采用了分治的思想。

分治算法的基本思想就是:当我们求解某些问题时,由于这些问题要处理的数据相当多,或求解过程相当复杂,使得直接求解法在时间上相当长,或者根本无法直接求出。对于这类问题,我们往往先把它分解成几个子问题,找到求出这几个子问题的解法后,再找到合适的方法,把它们组合成求整个问题的解法。如果这些子问题还较大,难以解决,可以再把它们分成几个更小的子问题,以此类推,直至可以直接求出解为止。

在大概了解了上面是分治算法之后,我们再来说说什么是归并排序。归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。

归并操作(merge),也叫归并算法,指的是将两个顺序序列合并成一个顺序序列的方法。

这样归并排序的原理就很容易理解了:首先,对于一个无序的长序列,可以把它分解为若干个有序的子序列,然后依次进行归并。如果我们说每一个数字都是单独有序的序列,那么只要把原始长序列依次分解,直到每个子序列都只有一个元素的时候,再依次把所有的序列进行归并,直到序列数为1,这样就完成了排序。

归并排序的速度仅次于快速排序,是稳定排序算法,归并排序的比较次数小于快速排序的比较次数,但移动次数一般多于快速排序的移动次数。

归并排序的实现由两种方法:

  • 自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法);
  • 自下而上的迭代;

①、递归法

原理为:

  1. 将原始序列从中间分为左、右两个子序列
  2. 将左序列和右序列再分别从中间分为左、右两个子序列
  3. 重复以上步骤,直到每个子序列都只有一个元素,可认为每一个子序列都是有序的
  4. 最后依次进行归并操作
#include <stdio.h>
void Merge(int sourceArr[],int tempArr[], int startIndex, int midIndex, int endIndex)
{
    int i = startIndex;
	int j = midIndex+1;
	int 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 
        sourceArr[i] = tempArr[i];
}
void MergeSort(int sourceArr[], int tempArr[], int startIndex, int endIndex)
{
    int midIndex;
    if(startIndex < endIndex)//判断是否分解为只有一个元素 
    {
        midIndex = (startIndex + endIndex)/2;//将序列一分为二 
        MergeSort(sourceArr, tempArr, startIndex, midIndex);//分解左边 
        MergeSort(sourceArr, tempArr, midIndex+1, endIndex);//分解右边 
        Merge(sourceArr, tempArr, startIndex, midIndex, endIndex);//归并 
    }
}
int main(int argc, char * argv[])
{
    int n,i,a[10],b[10];
    scanf("%d",&n);
    for(i=0;i<n;i++)
    	scanf("%d",&a[i]);
    MergeSort(a,b,0,n-1);
    for(i=0;i<n;i++)
        printf("%d ",a[i]);
    printf("\n");
    return 0;
}

②、迭代法

原理为:

  1. 将序列每相邻两个数进行归并操作,形成(n/2)个序列,排序后每个序列包含两/一个元素
  2. 将序列每相邻的两个有序子序列进行归并操作,形成(n/4)个序列,每个序列包含四/三个元素
  3. 重复步骤2,直到所有元素排序完毕,即序列数为1个
void Merge(int *a,int low,int mid,int high)
{
    int i=low,j=mid+1,k=0;
    int *temp = (int*)malloc((high-low+1)*sizeof(int));
    while(i<=mid&&j<=high)
    	a[i]<=a[j]?(temp[k++]=a[i++]):(temp[k++]=a[j++]);	
	while(i<=mid)
		temp[k++]=a[i++];	
	while(j<=high)
		temp[k++]=a[j++];	
	memcpy(a+low,temp,(high-low+1)*sizeof(int));
	free(temp);
}
void MergeSort(int *a,int n)
{
	int length;
	for(length=1;length<n;length*=2)
	{
		int i;
		for(i=0;i+2*length-1<=n-1;i+=2*length)
			Merge(a,i,i+length-1,i+2*length-1);
		if((i+length)<=(n-1))
 			Merge(a,i,(i+length-1),(n-1));
	}
}

6、快速排序

快速排序的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列 

快速排序也是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。

对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。

快速排序算法通过多次比较和交换来实现排序,其排序流程如下:  

(1)首先设定一个分界值,通过该分界值将数组分成左右两部分。一般选第一个元素为“基准数”  ,或者随机算一个

(2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。  

(3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。 

(4)重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了

#include <stdio.h> 
int a[101],n; 
void Quick_sort(int left,int right)
{    
	int i,j,t,temp;  
    if(left>right)
	    return; 
    temp=a[left]; //temp中存的就是基准数 
	i=left;  
	j=right; 
	while(i!=j)  
	{         
		while(a[j]>=temp && i<j)//顺序很重要,要先从右往左找  
			j--;   
			
		while(a[i]<=temp && i<j)//再从左往右找         
			i++;
			
		if(i<j)//当i和j没有相遇时 ,交换两个数在数组中的位置   
		{         
			t=a[i]; 
			a[i]=a[j];      
			a[j]=t;      
		}  
	}     
	//将基准数归位  
	a[left]=a[i];
	a[i]=temp;
	Quick_sort(left,i-1);//继续处理左边的
	Quick_sort(i+1,right);//继续处理右边的
} 
 
int main()
{     
	int i,j,t;    
	scanf("%d",&n); 
	for(i=0;i<n;i++)   
		scanf("%d",&a[i]); 
    Quick_sort(0,n-1);
	for(i=0;i<n;i++) 
	printf("%d ",a[i]);     
	return 0;
}

7、堆排序

堆排序是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。既然堆顶元素永远都是整棵树中的最大值,那么我们将数据构建成堆后,只需要从堆顶取元素不就好了吗? 第一次取的元素,就是最大值。取完后把堆重新构建一下,然后再取堆顶的元素,取的就是第二大的值。 反复的取,取出来的数据也就是有序的数据。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:

  1. 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
  2. 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;

用这个动图和代码结合,要好好理解堆排序的具体实现过程。

#include <stdio.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,j;
    for(i=len/2-1;i>=0;i--)//初始化,i从最后一个父节点(len/2-1)开始调整
    {
    	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[] = {5,2,7,3,6,1,4};
    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;
}

8、计数排序

计数排序的核心在于将输入的数据值转化为键,存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。当然这是一种牺牲空间换取时间的做法,而且当O(k)>O(n*log(n))的时候其效率反而不如基于比较的排序(基于比较的排序的时间复杂度在理论上的下限是O(n*log(n)), 如归并排序,堆排序)。

算法步骤:

  • 首先获取最小值 min 和最大值 max
  • 开辟一块新的空间创建新的数组,长度为(MAX-min+1)
  • 数组B中index的元素记录的值是A中某元素出现的次数
  • 最后输出目标整数序列

计数排序是有局限性的,如果要排序的数据范围非常大,也就是说,max-min的值非常大,计数排序就不太适用了。由此可见,计数排序只适用于正整数并且取值范围相差不大的数组排序使用,它的排序的速度是非常可观的。

#include<stdio.h>
void Counting_sort(int a[],int b[],int len,int k)
{
    if(a == NULL || k <= 0 || len <= 0)
        return;
        
    int count[k+1],i;
    
    for(i = 0;i <= k;i++)//初始化辅助数组
        count[i] = 0;
        
    for(i = 0;i < len;i++)//统计值为a[i]的个数,count[i]是等于i的元素个数
        count[a[i]] ++;

    for(i = 1;i <= k;i++)//对所有的计数累加 ,这里的目的是为了后面的输出做准备,确定他们的位置 
        count[i]+=count[i-1];
        
    for(i = len-1;i >= 0;i--)//输出到数组b中
	{
        int index = count[a[i]];//index是a[i]在数组b中的下标 
        b[index-1] = a[i];//b下标从0开始
        count[a[i]] --; //如果有相同值元素的情况
    }
}
int main()
{
    int a[100];
    int b[100]={0};
    int n,i;
    int max=-99999999;
    int min=99999999;
    scanf("%d",&n);
    for(i=0;i<n;i++)
    {
    	scanf("%d",&a[i]);
    	if(max<a[i])
			max=a[i];
		if(min>a[i])
			min=a[i];
    }
    int k=max-min+1;
    Counting_sort(a,b,n,k);
    for(i=0;i<n;i++)
	{
        printf("%d ",b[i]);
    }
    return 0;
}

  
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值