[排序算法] 常见排序算法原理及代码实现

插入排序----直接插入排序

思路:
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。

在这里插入图片描述

// 直接插入排序
void InsertSort(int* a, int n)
{	
	for (int i = 0; i < n - 1; i++)
	{
		//将tmp插入到[0,end]这个有序序列
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}
}

直接插入排序的特性总结:

  1. 元素集合越接近有序,直接插入排序算法的效率越高----O(N)
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1),它是一种稳定的排序算法
  4. 稳定性:稳定

插入排序----希尔排序

思路;
希尔排序法又称缩小增量法。基本思想是:先选定一个整数gap,把待排序文件中所有记录分成几个组,所有距离为gap的记录分在同一组内,并对每一组内的记录进行排序。然后取gap的值,重复上述分组和排序的工作。最后一次取gap=1,再经过最后一次排序后,该序列全部有序。

在这里插入图片描述

//改造直接插入排序
void ShellSort(int* a, int n) 
{
	int gap=n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;// +1 ---- 保证最后一次gap=1

		for (int i = 0; i < n - gap; ++i)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
          a[end + gap] = tmp;
		}
	}	
}

希尔排序的特性总结:

  1. 希尔排序是对直接插入排序的优化。
  2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就
    会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
  3. 希尔排序的时间复杂度不好计算,需要进行推导,推导出来平均时间复杂度: O(N^ 1.3—N^2)
  4. 稳定性:不稳定

选择排序----最简单的选择排序

思路:
遍历一次,找到剩余待排序序列中的最小或者最大值,分别放在该序列的左右两端,此时左右两端已经有序,继续遍历剩余的待排序序列

void SelectSort(int* a, int n) 
{
	int left = 0, right = n-1 ;
	while (left <= right)
	{
		int max = left, min = left;
		for (int i = left; i <= right; i++)
		{
			if (a[i] < a[min])
				min = i;
			if (a[i] > a[max])
				max = i;
		}
		swap(a[min], a[left]);
		//特殊错误处理
		if (left == max)
		{
			max = min;
		}
		swap(a[max], a[right]);
		left++;
		right--;
	}
}

这里的实现和普通选择排序稍有不同,这里是一次遍历找到两个值(最大值和最小值),然后分别交换到左右两边,使其左右两边变为有序。

选择排序----堆排序

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。
需要注意的是排升序要建大堆,排降序建小堆。

升序排序的步骤:

  1. 建大堆
  2. 将根结点(最大的结点)与end----最后一个结点交换
  3. end-=1;
  4. 重新执行向下调整算法,将次大的结点放到根上
  5. 循环第2,3,4步
// 先建堆
void AdjustDwon(int* a, int n, int root) 
{
	int parent = root;
	int child = 2 * parent + 1;
	while (child<n)
	{
		if (child + 1 < n&&a[child + 1] > a[child])
		{
			++child;
		}
		if (a[child]>a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
			break;
	}
}

//堆排序
void HeapSort(int* a, int n) 
{
	//升序 --建大堆;  降序 --键小堆
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDwon(a, n, i);
	}
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDwon(a, end, 0);
		--end;
	}
}

交换排序----冒泡排序

  1. 相邻元素进行比较,固定最大或最小值
  2. 两层f循环,第二层循环不再访问最后的有序数列

在这里插入图片描述

// 冒泡排序
void BubbleSort(int* a, int n) 
{
	for (int end = n; end > 0; end--)
	{
		int exchange = 0;
		for (int i = 1; i < end; i++)
		{
			if (a[i - 1] > a[i])
			{
				Swap(&a[i - 1], &a[i]);
				exchange = 1;
			}
		}
		if (exchange == 0)
			break;
	}
}

交换排序----★快速排序★

其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
在这里插入图片描述

hoare法


// 快速排序hoare版本
int PartSort1(int* a, int begin, int end) 
{
	int left = begin;
	int right = end;
	int key = left;
	while (left < right)
	{
		while (left < right&&a[right] >= a[key])
		{
			--right;
		}
		while (left < right&&a[left] <= a[key])
		{
			++left;
		}
		Swap(&a[left], &a[right]);
	}
	Swap(&a[key], &a[left]);
	return left;
}

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;
	int key = PartSort1(a, begin, end);

	//[begin,key-1]  [key+1,end]
	QuickSort(a, begin, key - 1);
	QuickSort(a, key + 1, end);
}

挖坑法


// 快速排序挖坑法
int PartSort2(int* a, int left, int right) 
{
	int key = a[left];	//挖坑
	while (left < right)
	{
		右边找小
		while (left < right&&a[right] >= key)
			--right;
		//放到左边的坑位,右边形成新的坑
		a[left] = a[right]; 
		//左边找大
		while (left < right&&a[left] <= key)
			++left;
		//放到右边的坑位,左边形成新的坑
		a[right] = a[left];
	}
	a[left] = key;
	return left;
}

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;
	int key = PartSort2(a, begin, end);

	//[begin,key-1]  [key+1,end]
	QuickSort(a, begin, key - 1);
	QuickSort(a, key + 1, end);
}

优化----三数取中法

如果序列接近有序的情况下使用快速排序,效率会非常低,因为基本每走一步都需要进行交换时间复杂度接近O(N^2),使用三数取中法,可以保证我们取到的基准值使比较接近中位数的,不会出现效率最低的那种情况。

步骤:取该序列的第一个最后一个,和中间元素这三者中的中位数,交换到序列首位作为基准值,然后再执行快速排序

int GetMidIndex(int *a, int left, int right)
{
	int mid = (left + right) >> 1;
	//left      mid        right
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if(a[right]>a[left])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
	else      //a[mid] < a[left]
	{
		if (a[right] > a[left])
		{
			return left;
		}
		else if (a[right] > a[mid])
		{
			return right;
		}
		else
		{
			return mid;
		}
	}
}

优化-小区间优化

为了减少快排递归调用的次数,减轻消耗,当快排区间已经较小时(通常为 < 20时),调用直接插入排序,对小区间进行排序。

前后指针法

定义两个指针:prev和cur;
prev和cur开始一前一后;
cur向后去找比key小的值,找到以后++prev,然后交换prev和cur位置的值
直到遍历完整个序列

非递归实现快排

通过对栈进行循环操作,来模拟快速排序递归调用的过程

  1. 将begin和end入栈,对总区间([begin,end])进行一次分区排序,找到基准值keyi的正确位置
  2. 将left,keyi-1入栈,将keyi+1,right入栈
  3. 然后循环将keyi左边的小区间调整为有序
  4. 再循环将keyi右边的小区间调整为有序
int PartSort1(int* a, int begin, int end)
{
	int left = begin;
	int right = end;
	int key = left;
	while (left < right)
	{
		while (left < right&&a[right] >= a[key])
			--right;
		while (left < right&&a[left] <= a[key])
			++left;
		swap(a[left], a[right]);
	}
	swap(a[key],a[left]);
	return left;
}

stack<int> st;
void QuickSortNOR(int *a,int begin,int end)
{
	st.push(begin);
	st.push(end);
	while (!st.empty())
	{
		int left, right;
		right = st.top();
		st.pop();

		left = st.top();
		st.pop();

		int keyi = PartSort1(a,	left, right);
		if (left < keyi-1)
		{
			st.push(left);
			st.push(keyi-1);
		}
		if (keyi+1 < right)
		{
			st.push(keyi + 1);
			st.push(right);
		}
	}
}

归并排序

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

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

算法思想:

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

在这里插入图片描述

//归并排序
void _MergeSort(int *a, int left,int right,int* tmp)
{
	if (left >= right)
		return;

	int mid = (left + right) >> 1;
	_MergeSort(a, left, mid, tmp);
	_MergeSort(a, mid+1, right, tmp);

	//两队有序子区间归并到tmp,然后拷贝回去
	int begin1 = left, end1 = mid;
	int begin2 = mid+1, end2 = right;
	int i = left;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
			tmp[i++] = a[begin1++];
		else
			tmp[i++] = a[begin2++];
	}
	while (begin1 <= end1)
		tmp[i++] = a[begin1++];
	while (begin2 <= end2)
		tmp[i++] = a[begin2++];

	//归并完成后,拷贝回原数组
	for (int j = left; j < right; j++)
		a[j] = tmp[j];
}

void MergeSort(int *a, int n)
{
	int* tmp = (int*)malloc(n*(sizeof(int)));
	if (tmp == NULL)
	{
		printf("malloc error!\n");
		exit(-1);
	}
	_MergeSort(a, 0, n - 1,tmp);
	free(tmp);
}

各排序算法对比

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值