排序算法合集:冒泡排序、选择排序、插入排序、希尔排序、堆排序、递归归并排序、非递归归并排序、快速排序

本文深入解读了多种排序算法,包括简单选择排序的最小元素选择、直接插入排序的元素交换,希尔排序的分组插入,堆排序的大根堆构建与交换,归并排序的分治合并,以及快速排序的分区与递归。展示了它们的时间复杂度和代码实例。
摘要由CSDN通过智能技术生成


全部代码在最后

部分排序算法的理解

由小到大排序,排序数组待排序数据从1号位置开始

简单选择排序

  • 从未排序序列中选择一个最小的,加入到已排好序列末尾。

直接插入排序

  • 当前结点之前的序列已全部排好,若当前值小于前一个结点,说明它需要换位置,把它先存起来,然后在它之前的序列中,所有比它大的序列后移一位,再把这个结点放到空出来的位置上。

希尔排序

  • 设置一个增量k,从第一个点开始,它以及与它相隔n个k位置的数看作一组,每个组内的几个数据用插入方式排好序;
  • 实际实现的方式是,从第一个点加上增量k+1的点,也就是第一个分组的第二个点的位置开始,往后逐个做各自组内的插入排序,改变分组,继续排序,直到分组的增量小于1时结束。

堆排序(大根堆

  • 堆是完全二叉树,所以一般用一维数组直接表示堆;
  • 下标为 i 的结点的父结点下标为 i/2,其左右子结点分别为 2i、2i + 1;
  • 当某结点的子孙已经是堆结构时,若它不满足堆,则对它做Heap,就是比较这个结点和它的左右孩子比较大小,找到最大的那一个与之交换, 对被交换了的子结点继续做Heap,直到交换的结点为叶子结点(结点孩子下标超过总数), 这样可以保证该结点做完heap后,包括该结点在内的往下的结构满足堆;
  • 创建大根堆的过程就是从最后一个非叶子结点开始,往前逐个做Heap;
  • 排序时,拿到一个大根堆,从最后一个树的结点开始,与根结点交换,交换后从树中去掉这个结点(做个标记就可以),对新的根结点做Heap,重复排序操作。

归并排序

  • 将序列每一个数字看作有序组,将他与相邻的组两两一组按顺序合并,合并后的序列又是一个有序序列,继续与相邻的两两一组合并 ,直到整个序列有序,
  • 递归方式,代码简洁易懂。找到中间位置,对它左右两边递归排好序,然后归并这两个串。
  • 非递归更节省空间,效率也高一些。从k=1开始,将序列分好组,然后两两一组归并,每个组大小为k,起点为i,终点为i+2k-1,分割为i+k-1,i从整个数组的第一个数据开始,每次增加2k;
  • 当最后剩下的数据大于一个组但是不满足两个组时(分组的起点s满足k<length-s+1<2k)
  • 当最后只剩下一个组时,不改变

快速排序:

  • 选取一个参考结点,把小于它的数放到它的左边,大于它的数放到右边;
  • 实现方法: 对序列的头和尾两个指针,向中间靠拢,找到不满足小于/大于的位置,将参考点与之交换, 直到low和high重合,说明已经找到参考的的实际位置,然后对它左右两边的数列递归,直到整个序列都为有序

时间复杂度

请添加图片描述

代码展示

#include<iostream>
using namespace std;

#define MAXSIZE 20

typedef struct
{
	int r[MAXSIZE + 1];
	int length;
}SqList;

void swap(SqList* L,int i, int j)
{
	int temp = L->r[i];
	L->r[i] = L->r[j];
	L->r[j] = temp;
}

//冒泡
void BubbleSort(SqList* L)
{
	int flag = 1;
	for (int i = 1; i < L->length && flag ==1; i++)
	{
		flag = 0;
		for (int j = L->length-1; j >=i; j--)
		{
			if (L->r[j - 1] > L->r[j])  //从小到大排序
			{
				swap(L, j - 1, j);
				flag = 1;
			}
		}
	}

}


//简单选择排序:从未排序序列中选择一个最小的,加入到已排好序列末尾
void SelectSort(SqList* L)
{
	int min,i,j;
	for (i = 1; i < L->length; i++)
	{
		min = i;
		for (j = i+1; j < L->length; j++)
		{
			if (L->r[j] < L->r[min])
				min = j;
		}
		swap(L, min, i);
	}
}

//直接插入排序:当前结点之前的序列已全部排好,若当前值小于前一个结点,说明它需要换位置,
//它先存起来,然后在它之前的序列中,所有比它大的序列后移一位,再把这个结点放到空出来的位置
void InsertSort(SqList* L)
{
	int i, j;
	for (i = 2; i < L->length; i++)
	{
		if (L->r[i] <L->r[i - 1])
		{
			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];
		}
	}
}

//希尔排序:设置一个增量k,从第一个点开始,它以及与它相隔n个k位置的数看作一组,
//每个组内的几个数据用插入方式排好序,
// 实际实现的方式是,从第一个点加上增量k+1的点,也就是第一个分组的第二个点的位置开始,
// 往后逐个做各自组内的插入排序
//改变分组,继续排序,直到分组的增量小于1时结束
void ShellSort(SqList* L)
{
	int i, j;
	int increment = (L->length-1)/2; //增量,每次减少一半
	while (increment >= 1)
	{
		for (i = increment + 1; i < L->length; i++)
		{
			if (L->r[i] < L->r[i - increment])
			{
				L->r[0] = L->r[i];
				for (j = i - increment; L->r[j] > L->r[0]; j -= increment)
					L->r[j + increment] = L->r[j];
				L->r[j + increment] = L->r[0];
			}
		}
		increment /= 2;
	}
}

//堆排序(大根堆
//堆是完全二叉树,所以一般用一维数组直接表示堆,
// 下标为 i 的结点的父结点下标为 i/2,其左右子结点分别为 2i、2i + 1,
//当某结点的子孙已经是堆结构时,若它不满足堆,则对它做Heap,
// 就是比较这个结点和它的左右孩子比较大小,找到最大的那一个与之交换,
// 对被交换了的子结点继续做Heap,直到交换的结点为叶子结点(结点孩子下标超过总数),
// 这样可以保证该结点做完heap后,包括该结点在内的往下的结构满足堆
// 创建大根堆的过程就是从最后一个非叶子结点开始,往前逐个做Heap
//排序时,拿到一个大根堆,从最后一个树的结点开始,与根结点交换,交换后从树中去掉这个结点(做个标记就可以)
//对新的根结点做Heap,重复排序操作
void Heap(SqList* L, int i, int n)
{
	int c1, c2;
	int max = i;
	c1 = 2 * i;
	c2 = 2 * i + 1;
	if (c2 < n && L->r[max] < L->r[c2])  //父小于右孩子
		max = c2;
	if (c1 < n && L->r[max] < L->r[c1])  //父小于左孩子
		max = c1;
	if (max != i)  //最大的结点不是父节点
	{
		swap(L, max, i);
		Heap(L, max, n);
	}
	else
		return;
}
void HeapSort(SqList* L) 
{
	for (int i = L->length / 2; i > 0; i--)  //先把序列建成大根堆
		Heap(L, i, L->length);

	for (int i = L->length - 1; i > 0; i--)
	{
		swap(L, i, 1);  //此时已经是大顶堆,先交换树根和最后一个结点
		Heap(L, 1, i);  //对根结点做Heap
	}
}


//归并排序
//将序列每一个数字看作有序组,将他与相邻的组两两一组合并,
// 合并后的序列又是一个有序序列,继续与相邻的两两一组合并 
//直到整个序列有序

void Merge(SqList *L, int s, int m, int t)  //归并函数
{
	int i, j, k= s;
	int LR[MAXSIZE];
	for (i = s, j = m + 1; i <= m&&j <= t; k++)  //这里之前犯了错误,把"&&"写成了","
	{
		if (L->r[i] < L->r[j])
			LR[k] = L->r[i++];
		else
			LR[k] = L->r[j++];
	}
	while (i <= m)
		LR[k++] = L->r[i++];
	while (j <= t)
		LR[k++] = L->r[j++];
	for (int w = s; w <= t; w++)
		L->r[w] = LR[w];
}
//递归方式,代码简洁易懂
//s/t:排序串开始/结束的下标
void MSort(SqList *L,int s,int t)  //排序函数
{
	int mid;  //存分组中间位置
	//先判断递归结束情况
	if (s == t)  //一个数字一组
		return;
	else  //不相等,将串的左右部分排序,然后归并
	{
		mid = (s + t) / 2;
		MSort(L, s, mid);
		MSort(L, mid + 1, t);
		Merge(L, s, mid, t);
	}
}
void MergeSort1(SqList* L)
{
	MSort(L,1,L->length-1);
}

//非递归
//非递归更节省空间,效率也高一些
void MergePass(SqList*L, int k, int length)
{
	int i = 1, j;
	while (i < length - 2 * k + 1)  //i的位置足够往后划分两个k大小的组
	{
		Merge(L, i, i + k - 1, i + 2 * k - 1);  //起始i,中间i+k-1,结束i+2*k-1
		i = i + 2 * k;  //下一个分组的起始
	}
	//剩余一个完整的k组和不完整组
	//这里忽略掉只剩一个不完整组的情况,当只剩一个不完整组时,不参与调整,而且它必然时是已排好序
	if (i < length - k + 1) 
		Merge(L, i, i + k - 1, length);
}
void MergeSort2(SqList* L)
{
	int k = 1;
	int* TR = (int*)malloc(L->length * sizeof(int));
	while (k < L->length)
	{
		MergePass(L, k, L->length-1);
		k = 2 * k;
	}
}


//快速排序
//很重要的排序算法
//先选取一个参考结点,把小于它的数放到它的左边,大于它的数放到右边,
//对序列的头和尾两个指针,向中间靠拢,找到不满足小于/大于的位置,将参考点与之交换
// 直到low和high重合,说明已经找到参考的的实际位置
// 然后对它左右两边的数列递归,直到整个序列都为有序,最内层的情况是low==high
void QSort(SqList* L, int low, int high)
{
	int pivot;
	int pivotkey;
	int i = low, j = high;
	if (i >= j)
		return;
	pivotkey = L->r[low];
	while (i < j)
	{
		//这里不要写成L->r[i] <= pivotkey,
		//因为我们现在选择i作为参考,写错了i的位置永远满足条件,它不会动
		while (i < j && L->r[i] <pivotkey) 
			i++;
		while (i < j && L->r[j] > pivotkey)
			j--;
		swap(L, i, j);
	}
	pivot = i;
	QSort(L, low, pivot - 1);
	QSort(L, pivot + 1, high);
}
void QuickSort(SqList* L)
{
	QSort(L, 1, L->length - 1);
}


int main()
{
	SqList L = {{-1,3,6,2,9,10,7,8,1,5,4},11}; //L.r[0]存其他数据
	cout << "排序前" << endl;
	for (int i = 1; i < 11; i++)
		cout << L.r[i] << " ";
//	BubbleSort(&L); //冒泡
//	SelectSort(&L);  //简单选择
//	InsertSort(&L);  //直接插入
//	ShellSort(&L);  //希尔排序
//	HeapSort(&L);  //堆排序
//	MergeSort1(&L);  //归并排序 递归
//	MergeSort2(&L);  //归并排序 非递归
	QuickSort(&L);  //快排
	cout << endl << "排序后" << endl;
	for (int i = 1; i < 11; i++)
		cout << L.r[i] << " ";
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值