【算法】七大排序

常见的有以下八中排序算法:

在这里插入图片描述
七大算法之间的时间复杂度、空间复杂度、稳定性的比较:
在这里插入图片描述

选择排序算法准则:

每种排序算法都各有优缺点。
影响排序的因素有很多,平均时间复杂度低的算法并不一定就是最优的。相反,有时平均时间复杂度高的算法可能更适合某些特殊情况。同时,选择算法时还得考虑它的可读性,以利于软件的维护。一般而言,需要考虑的因素有以下四点:

  • 1.待排序的记录数目n的大小;
  • 2.记录本身数据量的大小,也就是记录中除关键字外的其他信息量的大小;
  • 3.关键字的结构及其分布情况;
  • 4.对排序稳定性的要求。

设待排序元素的个数为n:
1)当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。

  • 快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
  • 堆排序 : 如果内存空间允许且要求稳定性的,
  • 归并排序:它有一定数量的数据移动,所以我们可能过与插入排序组合,先获得一定长度的序列,然后再合并,在效率上将有所提高。

2) 当n较大,内存空间允许,且要求稳定性 ——归并排序
3)当n较小,可采用直接插入或直接选择排序。

  • 直接插入排序:当元素分布有序,直接插入排序将大大减少比较次数和移动记录的次数。
  • 直接选择排序 :元素分布有序,如果不要求稳定性,选择直接选择排序

5)一般不使用或不直接使用传统的冒泡排序。
6)基数排序:
它是一种稳定的排序算法,但有一定的局限性:1、关键字可分解。2、记录的关键字位数较少,如果密集更好3、如果是数字时,最好是无符号的,否则将增加相应的映射复杂度,可先将其正负分开排序。

七大排序算法

1.选择排序
2.插入排序
3.快速排序
4.堆排序
5.冒泡排序
6.希尔排序
7.归并排序

1.选择排序

思想:遍历数组,每次遍历都在未排序的部分找到最小元素的下标,在此次遍历结束后将最小元素放到遍历开始的位置
性能:时间复杂度为O(n2),算法比较次数与初始序列状态无关,性能在所有排序算法中最差。
动态演示图
在这里插入图片描述

c++代码

void select_Sort(int* A, int len){
        for(int i=0;i<len;i++)
        {
                int k = i;
                for(int j=i;j<len;j++)
                        if(A[j]<A[k])
                                k = j;
                if(k != i){
                        int t = A[i];
                        A[i] = A[k];
                        A[k] = t;
                }
        }
}

2.插入排序

思想:将数组中的所有元素依次和前面的已经排好序的元素相比较(依次),如果选择的元素比已排序的元素小,则交换,直到全部元素都比较过。
实现:利用end为指向已经排好序的最后一个元素,temp声明为待排序的数,在以排好序的数列中寻找可以插入的位置,在寻找的过程中是从后向前寻找,发现比temp大的就向后移动,直到发现比temp小的终止寻找,并将temp放入该位置,进入到下一个循环。
动态演示图
在这里插入图片描述

3.快速排序 :

参考质料链接
思路:与归并排序类似,也使用分治思想,选择一个元素值(一般选择最后一个元素),将比它小的放在左边部分,比它大的放在右边,然后对两部分分别进行上述操作知道递归结束,关键步骤在于元素的分类,且只占用O(1)的额外存储空间。

步骤(小到大):选择最左边的数为基准数,利用双边循环法,右边先向前搜索,大于等于基准数就继续前进,否则停止,开始移动左边指针向前,小于基准数就后退,否则停止,此时交换左右指针指向的数值再次前进后退,知道左右二个指针相遇。相遇后与基准数交换数值。此时将原数组分成了二个部分,一部分大于基准数,一部分小于基准数。利用递归方法继续分裂,变成最简单的排序数列。

性能:时间复杂度为O(nlgn),与归并排序不同,该算法占用常数级额外存储,在大规模序列排序应用中性能较好。

c++代码

//快速排序(从小到大)
void quickSort(int left, int right, vector<int>& arr)
{
	if(left >= right)
		return;
	int i, j, base, temp;
	i = left, j = right;   //左右二个指针
	base = arr[left];  //取最左边的数为基准数
	while (i < j)
	{
		while (arr[j] >= base && i < j)   j--;  //移动右指针
		while (arr[i] <= base && i < j)   i++;  //移动左指针
		if(i < j){    //二个指针停止时  交换指向的数值
			temp = arr[i];
			arr[i] = arr[j];
			arr[j] = temp;
		}
	}
	arr[left] = arr[i];    //基准数归位
	arr[i] = base;
	//开始递归分裂   分治法体现在这里
	quickSort(left, i - 1, arr);//递归左边
	quickSort(i + 1, right, arr);//递归右边
}

4.堆排序 :

参考质料
思想:使用堆数据结构进行排序,堆是一种用数组存储的二叉树,根据父节点和子节点的大小关系分为最大堆和最小堆,这里使用最大堆进行排序。将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了

算法的步骤

  1. 把无序数组构建成二叉堆。需要从小到大排序,则构建成最大堆;需要从大 到小排序,则构建成最小堆。
  2. 循环删除堆顶元素,替换到二叉堆的末尾,调整堆产生新的堆顶。

性能
堆排序是一种选择排序,整体主要由构建初始堆+交换堆顶元素和末尾元素并重建堆两部分组成。其中构建初始堆经推导复杂度为O(n),在交换并重建堆的过程中,需交换n-1次,而重建堆的过程中,根据完全二叉树的性质,[log2(n-1),log2(n-2)…1]逐步递减,近似为nlogn。所以堆排序时间复杂度一般认为就是O(nlogn)级。空间复杂度:O(1).
动态演示图
在这里插入图片描述
c++代码

#include <stdio.h>

void swap(int array[],int x,int y){
	int key;
	key=array[x];
	array[x]=array[y];
	array[y]=key;
}

//从大到小排序 
//void Down(int array[],int i,int n){
//	int child=2*i+1;
//	int key=array[i];
//	while (child<n){
//		if (array[child]>array[child+1] && child+1<n) {
//			child++;
//		}
//		if (key>array[child]){
//			swap(array, i, child);
//			i=child;
//		}
//		else{
//			break;
//		}
//		child=child*2+1;
//	}
//}

//从小到大排序   使得i节点下沉满足堆对节点的要求
void Down(int array[],int i,int n){						//最后结果就是大顶堆 
	int parent=i;										//父节点下标
	int child=2*i+1;									//子节点下标  
	while (child<n){
		if (array[child]<array[child+1] && child+1<n) {	//判断子节点那个大,大的与父节点比较 
			child++;
		}
		
		if (array[parent]<array[child]){				//判断父节点是否小于子节点 
			swap(array, parent, child);					//交换父节点和子节点 
			parent=child;								//子节点下标 赋给 父节点下标 
		}

		child=child*2+1;								//换行,比较下面的父节点和子节点 
	}
}

void BuildHeap(int array[],int size)
{
   int i=0;						   //倒数第二排开始 从右到左  从小到上
   for(i=size/2-1;i>=0;i--){							//创建大顶堆,必须从下往上比较 
      Down(array,i,size);								//否则有的不符合大顶堆定义 
  	}
}

void heapSort(int array[],int size){
    BuildHeap(array,size);			//初始化堆(使非叶子节点都下沉) 
    printf("初始化数组:");
	for(int i=0;i<size;i++){
		printf("%d ",array[i]);
	}
	printf("\n");
	
    for(int i=size-1;i>0;i--)
    {	//交换最后一个节点与堆顶的节点,然后将堆顶节点做下沉处理
    	swap(array,0,i);								//交换顶点和第 i 个数据 
    	//因为只有array[0]改变,其它都符合大顶堆的定义,所以可以从上往下重新建立 
    	Down(array,0,i);								//重新建立大顶堆 
    	
    	printf("排序的数组:");
		for(int i=0;i<size;i++){
			printf("%d ",array[i]);
		}
		printf("\n");
	}
}

int main(){
	int array[]={49,38,65,97,76,13,27,49,10};
	int size= sizeof(array) / sizeof(int);
	printf("%d \n",size);
	printf("排序前数组:");
		for(int i=0;i<size;i++){
			printf("%d ",array[i]);
		}
	printf("\n");
	heapSort(array,size);
	return 0;
}

运行结果图
在这里插入图片描述

5.冒泡排序

思想:从左往右遍历,比较相邻两个元素的大小,将大的一个放在后面,每遍历一趟,可找到一个最大值放置在最后,经过n-1趟遍历即可。

性能:比较次数为:(n-1) + (n-2) + … + 1 = n*(n-1) / 2,因此冒泡排序的时间复杂度为O(n^2)。

步骤:
1.比较相邻的元素,前一个比后一个大(或者前一个比后一个小)调换位置
2.每一对相邻的元素进行重复的工作,从开始对一直到结尾对,这步完成后,结尾为做大或最小的数.
3.针对除了最后一个元素重复进行上面的步骤。
4.重复1-3步骤直到完成排序

动态演示图
在这里插入图片描述
c++代码

#include<stdio.h>
void BuddleSort(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 swap=a[j];
                a[j]=a[j+1];
                a[j+1]=swap;
            }
        }
    }
} 
int main(){
    int a[]={6, 98452137};
    int n=sizeof(a)/sizeof(int);
    BuddleSort(a,n);
    printf("冒泡排序结果:"); 
    for (int i = 0; i < n; i++){
        printf("%d ", a[i]);
    }
    return 0;
}

6.希尔排序(缩小增量排序 )

参考质料链接
简介:是简单插入排序的改进版,是将整个无序列分割成若干小的子序列分别进行插入排序的方法。它与插入排序的不同之处在于,它会优先比较距离较远的元素。简单插入排序对小规模数据数据或者基本有序时十分高效,数据有序程度越高,越高效。使用希尔排序对较大规模并且无序的数据会非常有效率。

算法步骤
首先它把较大的数据集合按照n/2的增量分割成若干个小组(逻辑上分组),然后对每一个小组分别进行插入排序,此时,插入排序所作用的数据量比较小(每一个小组),插入的效率比较高。每个分组进行插入排序后,各个分组就变成了有序的了(整体不一定序)。接着将增量再次缩小一倍,重复上面的操作,直到增量为1.

  • 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
  • 按增量序列个数k,对序列进行k 趟排序;
  • 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1时,整个序列作为一个表来处理,表长度即为整个序列的长度。
    性能:希尔排序的性能无法准确量化,跟输入的数据有很大关系。在实际应用中也不会用它,因为十分不稳定,虽然比传统的插入排序快,但比快速排序等慢。其时间复杂度介于O(nlogn) 到 O(n^2) 之间
    动画演示

    c++代码
void   shellsort(int  arr[],int len) {    //数组作为参数时,将长度代入会比较好,不然通过sizeof无法知道数组长度
	int n = len;  //数组长度
	for (int gap = n / 2; gap > 0; gap /= 2) {  //改变增量gap 进行插入排序 
		for (int i = gap; i < n; i++) { //对各分组的每个元素进行插入排序
			int  key = arr[i];
			int j;   //插入到合适的位置
			for (j = i - gap; j >= 0 && key < arr[j]; j -= gap) {
				arr[j + gap] = arr[j];  //排好的序列移位
			}
			arr[j + gap] = key;  //将待插入的数插入到合适的位置
		}
	}
}
int main() {
	int  sum[] = { 4,2,3,1,5,8};
	int len = sizeof(sum) / sizeof(sum[0]);
	shellsort(sum,len);
	int n = len;
	while (n)
	{
		cout << sum[len - n] << " ";
		n--;
	}
	return 0;
}

7.归并排序

简介
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

:分治思想的代表——首先把原问题分解为两个或多个子问题,然后求解子问题的解,最后使用子问题的解来构造出原问题的解。
算法描述

  • 把长度为n的输入序列分成两个长度为n/2的子序列;
  • 对这两个子序列分别采用归并排序;
  • 将两个排序好的子序列合并成一个最终的排序序列。

性能分析
归并排序是一种稳定的排序方法。和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(nlogn)的时间复杂度。代价是需要额外的内存空间。

动画演示
在这里插入图片描述
c++代码

const int N=1001;
int a[N];
int n;
 //左右表有序表进行合并
void Merge(int a[],int low,int mid,int high){
    int i,j,k;
    int b[N]; //复制a中数据
    for(int z=low;z<=high;z++)
        b[z]=a[z];
    i=low; //以mid为中心i遍历左边的表,j遍历右边的表,找出最小值放入a中
    j=mid+1;
    k=i;
    while(i<=mid&&j<=high){  //二表开始比较大小排入原数组,当左右序列任一个读完时结束
        if(b[i]<=b[j])
            a[k]=b[i++];
        else
            a[k]=b[j++];
        k++;
    }  
   //左右序列有一个没读完,继续读不用比较
    while(i<=mid) a[k++]=b[i++];
    while(j<=high) a[k++]=b[j++];
}
 
void MergeSort(int a[],int low,int high){
    if(low<high){
        int mid=(low+high)/2;  //在此是该序列一分为二,直到1-1,然后将1-1归并成有序的2
        MergeSort(a,low,mid);
        MergeSort(a,mid+1,high);
        Merge(a,low,mid,high);   //合并前面二个序列
    }//else  是low=high时
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
   MergeSort(a,1,n);
    for(int i=1;i<=n;i++)
        cout<<a[i]<<" ";
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值