【一篇文章搞定常见排序算法】插入,交换,选择,归并

插入排序

插入排序,顾名思义,就是以插入的方式将相应的数字插入到有序序列中的合适位置,继而产生新的有序序列。

最贴切的例子就是玩扑克牌时,会在游戏开始之前将扑克牌进行整理,手中的牌可以分成有序部分未定部分,取出未定部分的牌插入到有序部分的合适位置,最终得到的牌便是有序的。这个过程就运用了插入排序的方法。在这里插入图片描述

直接插入排序

具体步骤:

  1. 将待排序列分成两部分,有序序列和无序序列
  2. 依次将无序序列中的待插元素与有序序列的元素比较大小,移动有序序列中的元素,直到找到合适位置,放入待插元素
  3. 当无序序列中的元素全部插入完毕后,结束排序

图解:
给定一个 29,14,13,37,10 的序列,按升序大小排列
在这里插入图片描述在这里插入图片描述

算法描述:

void insert_sort(int* arr,int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int end = i; //有序末尾
		int tmp = arr[end + 1]; //待插元素
		while (end >= 0)   //确保插入位置不越界
		{
			if (tmp < arr[end])
			{
				
				arr[end + 1] = arr[end]; //移动有序序列中元素
				end--;
			}
			else  //找到了合适待插位置
			{
				break;
			}
		}arr[end + 1] = tmp; //放入待插元素
	}
}

算法分析:
时间复杂度:O(n2)
空间复杂度:O(1)
在最好情况下,原序列为非递减序列,可以达到只比较,不一定,时间复杂度接近O(N);
在最坏情况下,原序列为非递增序列,时间复杂度接近O(N2);
因此,对于一个接近有序的序列,使用此方法会得到事半功倍的效果。

希尔排序

希尔排序是将直接插入排序在“减少数据个数”和使“序列基本有序”两方面进行了优化。
因为不可避免的,有时候排序的序列为类似91,2,66,80,1的情况,如果直接实行直接插入排序,需要将91不断挪动,如果序列元素过多,则挪动的次数也就随之增长。
所以考虑如何将诸如此类较大元素快速挪到序列后
具体步骤:

  1. 将序列根据不同的增量分成相应的组,每个组内分别实行插入排序
  2. 减小增量,重新分组,重复步骤1
  3. 直到序列基本有序,增量设为1,实现直接插入排序
    希尔排序的实质是——分组插入,能够减少参与插入排序的数据量。

图解:
给定一个99,49,38,65,97,13,2,100,44 的序列,按升序大小排列
在这里插入图片描述
第一趟下来,99一下子就能移动到后面去,2,13等也能移动到靠前的位置,当前的序列已经接近有序,接下来使用直接插入排序即可
在这里插入图片描述
算法描述:

void Shellsort(int* arr, int n,int gap)  //每次传入不同的增量gap
{
	for (int i = 0; i < n-gap; i++)
		{
			int end = i;
			int tmp = arr[end + gap];
			while (end >= 0)
			{
				if (tmp < arr[end])
				{

					arr[end + gap] = arr[end];
					end-=gap;
				}
				else
				{
					break;
				}
			}arr[end + gap] = tmp;
		}
}

交换排序

冒泡排序

冒泡排序:两两比较相邻元素,根据需要交换。这样,n个元素的序列,经过一趟冒泡排序,比较了n-1次,使得最后一个元素的位置得到确认。
同理,n个元素的序列,为使得所有元素的位置得到确认,需经过n-1趟冒泡排序,第i趟冒泡排序,比较n-i次。这个算法会重复进行,直到没有两两需要交换的元素为止。
这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。

例如:对33,14,22,97,65,38,26,77,13,33 这一序列升序排序
在这里插入图片描述
前三趟下来,序列后三个位置确定;
以此类推,可以得到有序的序列

算法描述:

void BubbleSort(int *a, int n)
{
	int flag=0; //用来标记一趟排序是否发生交换
	for(int i=0;i<n;i++) //外循环为排序的趟数,n个数进行n-1趟
	{
		flag=0;
		for(int j=0;j<n-1-i;j++)  //内循环为两两比较的次数,第i趟比较n-i次
		{
			if(a[j+1]<a[j])
			{
				swap(&a[j-1],&a[j]);
				flag=1;
			}
		}
		if(flag==0)  //一趟排序没有发生交换,证明序列有序
		{
			break;
		}
	}
}

快速排序

快速排序是依据一个“中值”,将序列分成两部分,小于中值的一部分和大于中值的一部分。然后,每一部分分别进行快速排序(递归实现)。通过不断确定不同“中值”的位置,实现序列排序。

具体步骤:

  1. 序列的第一个元素定为“中值”,附设两个指针leftmark=1、 rightmark=length-1;
    2.将“中值”的位置记下,为hole
    在这里插入图片描述
    3.当leftmark<rightmark 时,向前移动rightmark,找到比“中值”小的元素位置,停下; 将该元素的值更新hole位置的值,同时更新hole=rightmark在这里插入图片描述
    在这里插入图片描述
    4.向后移动leftmark,找到比“中值”大的元素位置,停下;将该元素的值更新hole位置的值,同时更新hole=leftmark
    在这里插入图片描述
    在这里插入图片描述
    5.重复上述步骤,当leftmark==rightmark时,移动暂停,此时hole的位置是“中值”应该处于的位置,将中值填入hole中
    在这里插入图片描述
    在这里插入图片描述
    以上为一趟快速排序
    6.以中值为界,序列被分为两部分,将这两部分分别进行快速排序(递归)。
    7.分裂过程递归实现,结束条件为:length<=1(序列只有一个元素)
    在这里插入图片描述

算法描述:

void QSort(int *a,int n)
{
	QuickSort(a,0,n-1);
}
void QuickSort(int *a,int first, int last)
{
	if(first<last) //子序列长度大于1
	{
		int midvalue=Partition(a,first,last) //以“中值”为界,将子序列分为两部分,分别排序
		QuickSort(a,first,midvalue-1);
		QuickSort(a,midvalue+1,last);
	}
}
int Partition(int *a, int first, int last)
{
	int mid=a[first]; //记录“中值”大小
	int leftmark=first;
	int rightmark=last;
	int hole=first; //记录中值位置
	while(leftmark<rightmark)  //从序列两端向中间遍历
	{
		while(leftmark<rightmark && a[rightmark]>=a[mid])  //找比“中值”小的元素的位置,停下来
		{
			rightmark--;
		}
		a[hole]=a[rightmark]; 
		hole=rightmark;
		while(leftmark<rightmark && a[leftmark]<=a[mid])  //找比“中值”大的元素的位置,停下来
		{
			leftmark++;
		}
		a[hole]=a[leftmark];
		hole=leftmark;
	}
	
	a[hole]=mid;  //“中值”位置确定
	
	return hole;
}

算法分析:
如果分裂总能把序列分为相等两部分,时间复杂度O(logN)
移动是将每个元素都与中值比较,O(N)
平均时间复杂度O(NlogN)
最坏情况:当序列已经有序时,时间复杂度退化为O(N2),为避免最坏情况出现,需要改进中值的选取。采用三者取中原则,事先比较序列的第一个,最后一个,和中间的一个元素,选取居中的值作为“中值”,事先交换首元素和中值的位置。

int GetMidIndex(int* a, int begin, int end)
{
	int mid = (begin + end) / 2;
	if (a[begin] < a[mid])
	{
		if (a[mid] < a[end])
		{
			return mid;
		}
		else if (a[begin] < a[end])
		{
			return end;
		}
		else {
			return begin;
		}
	}
	else
	{
		if (a[begin] < a[end])
		{
			return begin;
		}
		else if (a[mid] < a[end])
		{
			return end;
		}
		else
		{
			return mid;
		}
	}
}

利用该函数可以得到“中值”位置
此算法适用于无序序列,n比较大的情况

选择排序

简单选择排序

选择排序的基本原理:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。

具体步骤:
1.从序列中选择最小的元素,放在序列的首端,它需要与序列的首元素交换;
2.选择第二小的元素,与序列的第二个元素交换;
3.以此类推,当选择进行到序列最后一个元素时,必然是最大元素。故选择只需进行n-1次,使排序完成。
在这里插入图片描述
算法描述:

void SelectionSort(int *a,int n)
{
	for(int i=0;i<n;i++)
	{
		k=i; //表示此趟排序中最小元素的下标
		for(int j=i+1;j<n;j++) //找出最小元素下标
		{
			if(a[j]<a[k])
			{
				k=j;
			}
		}
		if(k!=i) //将此趟最小元素放置已排序序列末尾
		{
			swap(&a[k],&a[i]);
		}
	}
}

算法分析:
时间复杂度O(N2)
空间复杂度O(1)

堆排序

实现堆排序首先要了解堆的特性。
大根堆——堆顶元素必为堆中元素的最大值
小根堆——堆顶元素必为堆中元素的最小值
在这里插入图片描述

堆排序可以分为两部分,建堆调整堆
建堆需要我们根据排序的要求,建立大根堆(升序)/小根堆(降序)
调整堆需要依据根叶节点关系,采用向下调整法,重新调整序列为堆。
堆排序具体步骤:
1.建堆
在这里插入图片描述

2.将堆顶元素与当前未经排序序列的最后一个元素交换,调整堆
在这里插入图片描述在这里插入图片描述

3.重复2,直至全部排序完毕
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
算法描述:

//建堆
void creatHeap(int *a,int n)
{
	for(int i=n/2;i>=0;i--)
	{
		HeapAdjust(a,i,n);
	}
}
//向下调整
void HeapAdjust(int *a, int parent,int size)
{
	int child=child1=parent*2+1;
	while(child<size)
	{
		int child2=child1+1;
		if(child2<size && a[child1]<a[child2])
		{
			child=child2;
		}
		if(a[parent]<a[child])
		{
			swap(&a[parent],&a[child]);
			parent=child;
			child=child1=parent*2+1;
		}
		else{
			break;
		}
	}
}
//堆排序
void HeapSort(int *a, int n)
{
	creatHeap(a,n); //建堆
	for(int i=n-1;i>0;i--) //调整堆
	{
		swap(&a[0],&a[i]);
		HeapAdjust(a,0,i);
	}
}

归并排序

归并排序是分治法的一个典型应用,通过将一个序列持续分裂为两部分(递归),对这两部分进行排序合并。
递归的结束条件:序列仅有1个元素时

具体步骤:

在这里插入图片描述
在这里插入图片描述
归并的过程中,核心是将两个相邻有序序列合并为一个有序序列
方法是,设置第三个序列,每次从给定的两个序列中分别取出一个元素,将较小者放到第三个序列中。重复此过程,直至其中一个序列为空,将剩余的元素放到第三个序列中。

算法描述:

void Merge(int *a,int *b,int low,int mid,int high)
{
	//序列1的起始区间
	int begin1=low;
	int end1=mid;
	//序列2的起始区间
	int begin2=mid+1;
	int end2=high;
	//合并序列的长度
	int k=low;
	while(begin1<=end1 && begin2<=end2)  //两个序列的归并
	{
		if(a[begin1]<a[begin2])
		{
			b[k++]=a[begin1++];
		}
		else{
			b[k++]=a[begin2++];
		}
	}
	//剩余的归并
	while(begin1<=end1)
	{
		b[k++]=a[begin1++];
	}
	while(begin2<=end2)
	{
		b[k++]=a[begin2++];
	}
}
void Msort(int*a,int low,int high,int*b)
{
	if(low<high)
	{
		int mid=(low+high)/2;
		Msort(a,low,mid,b);
		Msort(a,mid+1,high,b);
		Merge(a,b,low,mid,high);
	}
}
	
void MergeSort(int *a, int n)
{
	int *tmp=(int*)malloc(sizeof(int)*n);
	if (tmp == NULL)
	{
		return;
	}
	else
	{
		Msort(a,0, n-1, tmp);
	}
	free(tmp);
	tmp = NULL;
}

算法分析:
时间复杂度O(N*log2N)
空间复杂度O(N)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值