直接插入排序、希尔排序、冒泡排序、快速排序、简单选择排序及归并排序

版权声明:原创文章仅供大家学习使用,严禁转载 https://blog.csdn.net/qq_41809589/article/details/86580938

这六种排序算法在排序中非常常用,使用频率较高,故记录一下,以后自己忘了还能看一下。
直接插入排序:这个算法起源将一个数据插入到一个有序的数列中。从后往前一个一个比较,当找到插入点时,直接插入,不影响有序性。对于一个乱序的数列,将该算法重复使用,先排序前两个,再排序前三个…直到排到最后一个。

举例:将一个数插入到一个有序序列中,例如将flag插入R[i]中的i个有序的元素中(R[i]不是数组,单纯的i个元素,不过一般都用数组实现,实现时元素还是存在1-i,0要空出来),R[0]作为哨兵位,将待插入数据放到这儿,开始从后一个一个向前比较,如果大于待插入元素,将该值复制到下一个位置中,再向前比较,直到找到比待插入值小的元素,把R[0]的值插入到该位置后面。
在这里插入图片描述
for (j=i-1; R[0].key<R[j].key; --j)
R[j+1] = R[j]
循环结束表明应该插入到j+1上,以上是直接插入排序的最初思想,下面是完整实现过程。

 void InsertSort(int *a)
{
	int j;
	for (int i = 2; i <= length; i++)
	{
		if (a[i] < a[i - 1])
		{
			a[0] = a[i];
			a[i] = a[i - 1];
			for (j = i - 2; a[0] < a[j]; j--)
			{
				a[j + 1] = a[j];
			}
			a[j + 1] = a[0];
		}
	}
}

该排序最好的情况是已经排好的情况,没有数据移动,最坏的情况是反序情况,移动次数最多。在平均情况下得出该排序时间复杂度为O(n²),是稳定的排序方法


希尔排序:这是直接插入排序的升级版,它是将一个乱序数列按一个间隔分成若干组,在若干组中分别使用直接插入排序,再逐步减小这个间隔,每减小一次,在若干个组中分别进行一次排序,最后一次排序,整体为一个组,进行直接插入排序后完成。看起来比较麻烦,实际上它实现起来确实很麻烦(个人对代码实现的吐槽)。但它在进行前面几次排序后,数据基本已经达到有序,后面的移动次数会大大减小,经过多次试验表明是一种较为高效的算法。

下面是希尔排序的过程,步长一次为3、2、1,必须要注意的是最后一次步长一定为1。
根据两个25的位置可知希尔排序是不稳定的排序方法
在这里插入图片描述

void ShellInsert(int *a, int dk)//一次希尔排序实现
{
	for (int i = dk + 1; i <= length; i++)
	{
		if (a[i] < a[i - 1])
		{
			int j;
			a[0] = a[i];
			for (j = i - dk; j > 0 && a[0] < a[j]; j -= dk)
			{
				a[j + dk] = a[j];
			}
			a[j + dk] = a[0];
		}
	}
}
void ShellSort(int *a, int dlta[], int t)//反复使用希尔排序
{
	for (int k = 0; k < t; k++)
	{
		ShellInsert(a, dlta[k]);
	}
}

希尔序列在使用中要用到步长,每次的步长应该定为多少合适呢?目前还没有一个很好的证明,但可以确定的是最后一次步长一定为1。对于希尔排序时间复杂度证明为O(n1+a) (0<a<1),a的大小与增量序列的选取有关。


冒泡排序:这种方法思想很简单,假如对一个乱序数列中n个元素进行升序排列,先让前两个数比较大小,如果前面的数大则与后面的数交换位置,在比较第二个和第三个,第三个第四个…像冒泡一样把大的数据换到后面,但这只是一趟排序。一般来讲冒泡排序要进行n-1趟,但在实际使用中一般会建一个标记,如果在某一趟排序中没有发生数据交换,则可以直接跳出,来减少比较次数,排序完成。
在这里插入图片描述

void swap(int a, int b)
{
	int temp;
	temp = a;
	a = b;
	b = temp;
}
void bubbleSort(int V[], int n)
{
	int i = 1;
	int exchange = 1;
	while (i < n && exchange)
	{
		exchange = 0;	//标志置为0,假定未交换
		for (int j = 1; j <= n - i; j++)
		{
			if (V[j] > V[j + 1])
			{  //逆序  
				swap(V[j], V[j + 1]);//交换
				exchange = 1;   	//标志置为1,有交换 
			}
		}
		i++;
	}
}

当然很显然这种最好情况和最坏情况与直接插入排序一样,时间复杂度为o(n²),是一种稳定的排序算法。


快速排序:排序肯定以快为优,那为什么把快速这么好的一个名字给了这个算法呢,因为它真的很快,是现在排序算法中使用最多的算法之一。它的思想很精妙,是一种递归排序,可以直接把数据放在排序完成后属于它的位置上。在一个乱序数列中,把第一个当做一个flag,把flag的值存起来防止丢了(因为在这个排序中可能有值要放到它的位置上),再建立两个指针分别指向第一个值(就是那个flag)和最后一个值,假设为p1(前)和p2(后)。让p2所指向的值与flag比较,如果大于flag,说明排序完成后这个数应该在flag后面,则p2向前移动一次继续比较;如果小于flag,则把这个值赋到flag的位置上(因为保存过了,不用怕flag的值丢),此时p2不要移动,让p1指向的值和flag值比较,如果flag的值大,p1向后移动一次;如果p1指向的值大,则把该数赋到p2目前所指的位置上,再把p2向前移依次往复直到p1等于p2。再把flag的值赋到两指针相遇的地方,此时完成了一趟排序,flag已经确定了最终的位置,再把flag两边的数列用这种方法递归使用进行排序即可。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
emmmm…中间过程就省略了,直接到最后一步。
在这里插入图片描述
到此完成了第一趟排序,52已经确定了位置。
可见,经过“一次划分” ,将关键字序列{52, 49, 80, 36, 14, 58, 61, 97, 23, 75 }
调整为: {23, 49, 14, 36},52,{58, 61, 97, 80, 75}。
完整排序代码如下:

void quicksort(int *a, 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)
		{
			t = a[i];
			a[i] = a[j];
			a[j] = t;
		}
	}
	a[left] = a[i];
	a[i] = temp;
	quicksort(a,left, i - 1);
	quicksort(a,i + 1, right);
}

快速排序时间复杂度为O(n log2n ), 就平均计算时间而言, 快速排序是所有内排序方法中最好的一个,但它是一种不稳定的排序方法


简单选择排序:这个方法和它的名字一样,理解起来很简单。第一趟把乱序数列中最小的数与现在第一个数互换位置;第二趟把数列中除了第一个后最小的数与第二个数互换位置;第三趟把数列中除了第一个和第二个后最小的数与第三个数互换位置…反复使用即可完成排序。
在这里插入图片描述
在这里插入图片描述

int selectmin(int *p,int i)//找最小值
{
	int temp=p[i];
	int cnt=i;
	for(int j=i+1;j<=length;j++)
	{
		if(temp>p[j])
		{
			temp=p[j];
			cnt=j;
		}
	}
	return cnt;
} 
void selectsort(int *p)
{
	for(int i=1;i<length;i++)
	{
		int j=selectmin(p,i);
		if(i!=j)
		{
			swap(p[i],p[j]);
		}
	}
}

简单选择排序时间复杂度为O(n²),是一种不稳定的排序算法。


归并排序:这种排序思想起源于将两个有序数列排列成一个有序数列。第一趟把乱序数列中两两划分为一组,多出来的单独一组,在组中使用这种排序;第二趟将上一趟中每两组划分为一组,在新的组中使用这种排序…当剩最后两组时再用这个算法就可完成排序。
两路归并 (2-way merging)原始序列initList[ ]中两个有序表 initList[i] … initList[m]和initList[m+1] … initList[n],它们可归并成一个有序表, 存于另一对象序列mergedList的 i … n 中。在这里插入图片描述

void merge(int sr[],int tr[],int i,int m,int n)
{
	int j,k;
	for(j=m+1,k=i;i<=m&&j<=n;k++)
	{
		if(sr[i]<=sr[j]) 
		{
			tr[k]=sr[i++];
		}
		else
		{
			tr[k]=sr[j++];
		}
	}
	if(i<=m)
	{
		com4++;
		for(i;i<=m;i++)
		{
			tr[k++]=sr[i];
		}
	}
	if(j<=n)
	{
		for(j;j<=n;j++)
		{
			tr[k++]=sr[i];
		}
	}
}
void msort(int sr[],int tr1[],int s,int t)
{
	if(s==t) 
	{
		tr1[s]=sr[s];
	}
	else
	{
		int m=(s+t)/2;
		msort(sr,tr2,s,m);// 递归地将SR[s..m]归并为有序的TR2[s..m]
		msort(sr,tr2,m+1,t); //递归地SR[m+1..t]归并为有序的TR2[m+1..t]
		merge(tr2,tr1,s,m,t); // 将TR2[s..m]和TR2[m+1..t]归并到TR1[s..t]
	}
}
void mergesort(int *p)
{
	msort(p,p,1,length);
}

容易看出,对 n 个记录进行归并排序的时间复杂度为Ο(nlogn)。即:每一趟归并的时间复杂度为 O(n),总共需进行 [log2n]趟。归并排序是一种稳定的排序算法

猜你喜欢

转载自blog.csdn.net/qq_41809589/article/details/86580938