数据结构随笔——各大排序算法梳理及代码

一、简单概述

首先要解释几个术语:

稳定排序:如果 a 原本在 b 的前面,且 a == b,排序之后 a 仍然在 b 的前面,则为稳定排序。

不稳定排序:如果 a 原本在 b 的前面,且 a == b,排序之后 a 可能不在 b 的前面,则为非稳定排序。

原地排序:原地排序就是指在排序过程中不申请多余的存储空间,只利用原来存储待排数据的存储空间进行比较和交换的数据排序。

非原地排序:需要利用额外的数组来辅助排序。

排序所用的结构:

#define MAXSIZE 10
typedef struct
{
	int r[MAXSIZE + 1];/*用于存储要排序数组,r[0]用作哨兵或临时变量*/
	int length;
}list;

算法比较

在这里插入图片描述

二、冒泡排序

基本思想是,首先将第1个和第2个记录的关键字比较大小,如果是逆序的,就将这两个记录进行交换,再对第2个和第3个记录的关键字进行比较,依次类推,直至完成第(n一1)个和第n个记录的关键字之间的比较,此后,再按照上述过程剩下的排序,直至整个序列有序为止。

排序过程中要特别注意的是,当相邻两个元素大小一致时,这一步操作就不需要交换位置,因此也说明冒泡排序是一种严格的稳定排序算法,它不改变序列中相同元素之间的相对位置关系。

代码:

void Bubble_sort(list* l)
{
	for (int i = 1; i < l->length; i++)
	{
		for (int j = l->length - 1; j >= i; j--)/*从后往前*/
		{
			if (l->r[j] > l->r[j + 1])
				swap(l->r[j], l->r[j + 1]);
		}
	}
}

改进代码

如果说在一遍循环中,没有任何数据交换,这就说明此序列已经有序。根据这一点可以增加一个flag作为标记。

void Bubble_sort2(list* l)
{
	bool flag = true;
	for (int i = 1; i < l->length&&flag; i++)
	{
		flag = false;
		//没有任何数据交换,这就说明此序列已经有序,
		for (int j = l->length - 1; j >= 1; j--)
		{
			if (l->r[j] > l->r[j + 1]) 
			{
				swap(l->r[j], l->r[j + 1]);
				flag = true;
			}
		}
	}
}

三、选择排序

简单选择排序法(Simple Selection Sort)就是通过n-i次关键字间的比较,从n-i+ 1个记录中选出关键字最小的记录,并和第i (1≤ i ≤n)个记录交换之。

void select_sort(list* l)
{
	for (int i = 1; i < l->length; i++)
	{
		int min = i; /*将当前下标定义为最小值下标 */
		for (int j = i + 1; j <= l->length; j++) 
		/*循环之后的数据 */
		{
			if (l->r[min] > l->r[j])
				min = j;
		}
		if (min != i)/*若不等于,找到最小值*/
			swap(l->r[i], l->r[min]);
	}
}

四、直接插入排序

直接插入排序(Straight Insertion Sort)的基本操作是将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增1的有序表。

代码

void insert_sort(list* l)
{
	int i, j;
	for ( i = 2; i <= l->length; i++)
	{
		if (l->r[i] < l->r[i - 1]) /*需将L->r[i]插入有序子表*/
		{
			l->r[0] = l->r[i];
			for ( j = i - 1; l->r[j] > l->r[0]; j--)
				l->r[j + 1] = l->r[j];//记录后移
			l->r[j + 1] = l->r[0];
		}
	}
}

五、希尔排序

基本思想:算法先将要排序的一组数按某个增量d分成若干组,每组中
记录的下标相差d。对每组中全部元素进行排序,然后再用一个较小的增量
对它进行,在每组中再进行排序。当增量减到1时,整个要排序的数被分成
一组,排序完成。

代码

void shell_short(list* l)
{
	for (int dk = l->length / 2; dk >= 1; dk = dk / 2)
	{//增量序列
		for (int i = dk + 1; i <= l->length; i++)
		{
			if (l->r[i] < l->r[i - dk])
			{
				l->r[0] = l->r[i];//暂存在l->r[0]
				int j;
				for (j = i - dk; j > 0 && l->r[0] < l->r[j]; j-=dk)
				{//记录后移寻找查找位置
					l->r[j + dk] = l->r[j];
				}
				l->r[j+dk] = l->r[0];//插入
			}
		}
	}
}

算法中巧妙地地方在于,同时对所有数据进行希尔排序,增量为循环量,第二个循环依次访问一组中的全部元素进行插入排序。

六、归并排序

基本思想:将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。

在这里插入图片描述

算法代码

void merge(int SR[],int TR[],int i,int m,int n)
{ /*将有序的SR[i..m]和SR[m+1..n]归并为有序的TR[i..n] */
	int j, k, l;
	for (j = m + 1, k = i; i <= m && j <= n; k++)
	{//j为中间分割点
		if (SR[i] < TR[j])
			TR[k] = SR[i++];
		else
			TR[k] = SR[j++];
	}
	if (i <= m)
	{
		for (l = 0; l <= m - i; l++)
			TR[k + l] = SR[i + l];
	}
	if (j <= n)
	{
		for (l = 0; l <= n - j; l++)
			TR[k + l] = SR[j + l];
	}
}

递归实现

void msort(int SR[],int TR1[],int s,int t)
//把SR[s...t],归并排序为TR1[s...t]
{
	int m;
	int TR2[MAXSIZE + 1];
	if (s == t)
		TR1[s] = SR[s];
	else
	{
		m = (s + t) / 2;
		msort(SR, TR2, s, m);
		msort(SR, TR2, m + 1, t);
		merge(TR1, TR2, s, m, t);
		/*将TR2[s..m]和TR2 [m+1..t],归并到TR1[s..t]*/
	}
}

非递归写法

void merge_pass(int SR[], int TR[], int s, int n)
{/*将SR[]中相邻长度为s的子序列两两归并到TR[] */
	int i = 1;
	while (i <= n - 2 * s + 1)
	{
		merge(SR, TR, i, i + s - 1, i + 2 * s - 1);
		i = i + 2 * s;
	}
	if (i < n - s + 1)/*归并最后两个序列*/
	{
		merge(SR, TR, i, i + s - 1, n);
	}
	else//只剩下单个子序列
	{
		for (int j = i; j <= n; j++)
			TR[j] = SR[j];
	}
}
void meger_sort2(list* l)
{
	int* TR = (int*)malloc(l->length * sizeof(int));
	int k = 1;
	while (k < l->length)
	{
		merge_pass(l->r, TR, k, l->length);
		k = k * 2;
		merge_pass(TR, l->r, k, l->length);
		k = k * 2;
	}
}

七、快速排序

基本思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序。

排序流程

  • 首先设定一个分界值,通过该分界值将数组分成左右两部分。
  • 将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。
  • 然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
  • 通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。
void quicksort(int left, int right, list* l)
{
	if (left >= right)
		return;
	int i = left;
	int j = right;
	int base = l->r[left];/*取最左为基准数*/
	while (i < j)
	{
		while (l->r[j] >= base && i < j)
			j--;
		while (l->r[i] <= base && i < j)
			i++;
		if (i < j)
			swap(l->r[i], l->r[j]);
	}
	l->r[left] = l->r[i];
	l->r[i] = base;
	quicksort(left, i - 1, l);
	quicksort(i + 1, right, l);
}

八、堆排序

堆(Heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵完全二叉树的数组对象。

每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆。反之每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。

基本思想:将待排序的序列构造成一个大顶堆。 此时,整个序列的最大值就是堆顶的根结点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次小值。如此反复执行,便能得到一个有序序列了。

/*已知L->r[s. .m]中记录的关键字除L->r[s]之外均满足堆的定义*/
/*本函数调整L->r[s]的关键字,使L->r[s..m]成为一个大顶堆*/
void head_adjust(list* l, int s, int m)
{
	int temp = l->r[s];
	int j;
	for ( j = 2 * s; j <= m; j *= 2)
	{//沿关键字较大的孩子节点向下
		if (j < m && l->r[j] < l->r[j + 1])
			j++;
		if (temp >=l->r[j])
			break;//temp该插入的位置
		l->r[s] = l->r[j];
		s = j;
	}
	l->r[s] = temp;//插入
}
void head_sort(list* l)
{
	for (int i = l->length / 2; i > 0; i--)
		head_adjust(l, i, l->length);
	for (int i = l->length; i > 1; i--)
	{
		swap(l->r[1], l->r[i]);
		head_adjust(l, 1, i - 1);//调整1-(i-1)重新为大顶堆
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值