数据结构-常见排序算法概念及实现

本文详细介绍了常见的几种排序算法,包括插入排序、希尔排序、选择排序(如冒泡排序和堆排序)、交换排序(如快速排序)以及归并排序。讨论了它们的时间复杂度、稳定性以及实现方式,特别是对快速排序的几种优化方法进行了探讨。
摘要由CSDN通过智能技术生成


排序:使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
内部排序:数据元素全部放置在内存中的排序。
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能再内外存之间移动数据的排序。

常见的排序算法:

  1. 插入排序:
    ①直接插入排序
    ②希尔排序
  2. 选择排序:
    ①选择排序
    ②堆排序
  3. 交换排序:
    ①冒泡排序
    ②快速排序
  4. 归并排序:
    归并排序

常见排序的实现:

1. 插入排序:

直接插入排序:

给已经有序的序列中插入新的数据(假设递增)
假设:第一个数据有序
插入过程:从有序序列的最后一个位置向前遍历,找到第一个小于待插入数据的位置,待插入的数据放入已找到位置的下一个位置。

//时间复杂度O(n^2)
//数据有序时:O(n)
void insertSort(int* arr, int n)
{
	//假设第一个数据有序
	//未插入数据:[1,n]
	for (int i = 1; i < n; ++i)
	{
		//从有序数据的最后一个位置向前遍历
		int end = i - 1;
		int data = arr[i];
		while (end >= 0 && arr[end] > data)
		{
			//大的数据向后移动
			arr[end + 1] = arr[end];
			end--;
		}
		arr[end + 1] = data;
	}
}

希尔排序:

让数据越来越接近有序,减少数据移动的次数,调高插入排序的性能。
在这里插入图片描述

//时间复杂度:n^1.3
//稳定性:不稳定
void shellSort(int* arr, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;
		//最后一趟排序,间隔必须是1,保证所有数据都在同一组
		//一趟希尔排序
		for (int i = gap; i < n; ++i)
		{
			//同一组数据,最后一个有序数据的位置
			int end = i - gap;
			//待插入的数据
			int data = arr[i];
			while (end >= 0 && arr[end] > data)
			{
				arr[end + gap] = arr[end];
				end -= gap;
			}
			arr[end + gap] = data;
		}
	}
}

2. 选择排序

选择排序:

递增
①每一次从未排序的数据中找到一个最小的
②把最小的放到未排序的数据的头部,继续执行第一步

void Swap(int* arr, int pos1, int pos2)
{
	int tmp = arr[pos1];
	arr[pos1] = arr[pos2];
	arr[pos2] = tmp;
}

//选择排序
//时间复杂度:O(n^2)
//稳定性:稳定
void selectSort(int* arr, int n)
{
	//从未排序的序列中找最值,存放到未排序的起始位置
	//未排序的区间:[start,end]
	int start = 0;
	int end = n - 1;
	while (start < end)
	{
		int minIdx = start;
		int i;
		for (i = start + 1; i <= end; ++i)
		{
			if (arr[i] < arr[minIdx])
				minIdx = i;
		}
		//把最小值存开始位置
		Swap(arr, start, minIdx);
		//剩余未排序的空间[start+1, end]
		++start;
	}
}

void selectSort2(int* arr, int n)
{
	int start = 0;
	int end = n - 1;
	//每次从未排序的区间找到一个最大值和一个最小值
	//最小值放在头部,最大值放在尾部
	//遍历的次数减少一半
	while (start < end)
	{
		int minIdx = start;
		int maxIdx = start;
		for (int i = start + 1; j < +end; ++i)
		{
			if (arr[i] < arr[minIdx])
				minIdx = i;
			if (arr[i] > arr[maxIdx])
				maxIdx = i;
		}
		//最小值放在头部
		Swap(arr, start, minIdx);
		//判断最大值的位置是否在起始位置
		if (maxIdx == start)
			maxIdx = minIdx;
		//最大值放在尾部
		Swap(arr, end, maxIdx);
		++start;
		--end;
	}
}

堆排序:

指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。通过堆来进行选择数据。需要注意的是升序要建大堆,排降序建小堆。
递增:
建大堆:从最后一个非叶子节点开始(该节点位置为: (n-2) / 2 ),执行向下调整
交换根节点和最后一个叶子节点的位置,执行向下调整(最后一个叶子节点不进行操作)

//交换函数实现
void Swap(int* arr, int pos1, int pos2)
{
	int tmp = arr[pos1];
	arr[pos1] = arr[pos2];
	arr[pos2] = tmp;
}

//堆排序
//时间复杂度:O(nlog(n))
//稳定性:不稳定

//向下调整操作
void shiftDown(int* arr, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && arr[child + 1] > arr[child])
			++child;
		if(arr[child] > arr[parent])
		{
			Swap(arr, child, parent);
			parent = child;
			child = parent * 2 + 1;
		}
		else
			break;
	}
}
//堆排序实现
void heapSort(int* arr, int n)
{
	for (int i = (n - 2) / 2; i >= 0; --i)
	{
		shiftDown(arr, n, i);
	}
	int end = n - 1;
	while (end > 0)
	{
		Swap(arr, end, 0);
		shiftDown(arr, end, 0);
		--end;
	}
}

3. 交换排序:

冒泡排序:

(假设递增)
相邻元素进行比较,大的元素向后移动

//交换函数实现
void Swap(int* arr, int pos1, int pos2)
{
	int tmp = arr[pos1];
	arr[pos1] = arr[pos2];
	arr[pos2] = tmp;
}

//冒泡排序
//时间复杂度:O(n^2),数据有序O(n)
//稳定性:稳定
void bubbleSort1(int* arr, int n)
{
	//相邻元素进行比较
	//每一次遍历的范围:0~未排序数据的最后一个位置
	int end = n;
	while (end > 0)
	{
		//flag:标记一轮冒泡排序是否发生了交换操作
		int flag = 0;
		//一轮冒泡排序
		for (int i = 1; i < end; ++i)
		{
			if (arr[i - 1] > arr[i])
			{
				//大的向后移动
				Swap(arr, i - 1, i);
				flag = 1;
			}
		}
		//如果没有发生交换,说明剩余元素全部有序
		if (!flag)
			break;

		--end;
	}
}
void bubbleSort2(int* arr, int n)
{
	//相邻元素进行比较
	//每一次遍历的范围:0~未排序数据的最后一个位置
	for (int i = 0; i < n-1; i++)	//比较的轮数
	{
		for (int j = 0; j < n-1-i; j++)	//每轮比较的次数
		{
			if (arr[j] > arr[j + 1])
				Swap(arr, j, j + 1);
		}
	}
}

快速排序:

为Hoare于1962年提出的一种二叉树结构的交换排序方法。
基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
快速排序如果不做优化容易导致栈的溢出
①horae法:
选取一个基准值,
从剩余元素中开始遍历:
1从后往前找到第一个小于基准值的数据(如递减:从后往前找大的)
2从前往后找到第一个大于基准值的数据(如递减:从前往后找小的)
3交换找到的两个值
4从交换的位置,分别开始,继续执行第一步
结束:用相遇位置的数据和基准值进行交换

//交换函数实现
void Swap(int* arr, int pos1, int pos2)
{
	int tmp = arr[pos1];
	arr[pos1] = arr[pos2];
	arr[pos2] = tmp;
}

//快速排序
//时间复杂度:
//理想情况:划分均匀O:(nlog(n))
//最坏情况:数据有序:O(n^2)

//获取基准值:三数取中法:起始,中间,结束
int getMid(int* arr, int begin, int end)
{
	int mid = begin + (end - begin) / 2;
	if (arr[begin] > arr[mid])
	{
		if (arr[mid] > arr[end])
			//begin > mid > end
			return mid;
		else if (arr[begin] > arr[end])
			//begin > end > mid
			return end;
		else
			//end > begin > mid
			return begin;
	}
	else
	{
	//mid > begin
		if (arr[mid] < arr[end])
			//end > mid > begin
			return mid;
		else if (arr[begin] < arr[end])
			//mid > end > begin
			return end;
		else
			//mid > begin > end
			return begin;
	}
}
//返回划分之后,基准值所在的位置
int partion(int* arr, int begin, int end)
{
	//获取基准值位置
	int mid = getMid(arr, begin, end);
	//把基准值放到起始位置
	Swap(arr, begin, mid);
	int key = arr[begin];
	int start = begin;
	while (begin < end)
	{
		//从后往前找小于基准值的位置
		while (begin < end && arr[end] >= key)
			--end;
		//从前往后找大于基准值的位置
		while (begin < end && arr[begin] <= key)
			++begin;
		//交换
		Swap(arr, begin, end);
	}
	//交换基准值和相遇位置的数据
	Swap(arr, start, begin);
	return begin;
}
//快速排序实现
void quickSort(int* arr, int begin, int end)
{
	if (begin >= end)
		return;
	//div:一次划分后,基准值位置
	int div = partion(arr, begin, end);
	//左右两部分进行快速排序
	//[begin,div-1]
	//[div+1,end]
	quickSort(arr, begin, div - 1);
	quickSort(arr, div + 1, end);
}

②填坑法:

//填坑法
int partion2(int* arr, int begin, int end)
{
	//获取基准值位置
	int mid = getMid(arr, begin, end);
	//把基准值放到起始位置
	Swap(arr, begin, mid);
	//第一个值作为基准值,第一个位置为初始的坑的位置
	int key = arr[begin];
	int start = begin;
	while (begin < end)
	{
		//从后往前找小于基准值的位置
		while (begin < end && arr[end] >= key)
			--end;
		//填坑
		arr[begin] = arr[end];
		//从前往后找大于基准值的位置
		while (begin < end && arr[begin] <= key)
			++begin;
		//填坑
		arr[end] = arr[begin];
	}
	arr[begin] = key;
	return begin;
}
//快速排序实现
void quickSort(int* arr, int begin, int end)
{
	if (begin >= end)
		return;
	//div:一次划分后,基准值位置
	int div = partion2(arr, begin, end);
	//左右两部分进行快速排序
	//[begin,div-1]
	//[div+1,end]
	quickSort(arr, begin, div - 1);
	quickSort(arr, div + 1, end);
}

③前后指针法:
prev:上一个小于基准值的位置
cur:下一个小于基准值的位置
当cur走到一个小于基准值的位置,则:
判断prev和cur是否连续:
如果连续:区间[prev,cur]的值都是不大于基准值,更新prev,cur
如果不连续:区间[prev,cur]的值,有大于基准值的,更新prev,数据交换,更新cur

//前后指针法
int partion2(int* arr, int begin, int end)
{
	//上一个小于基准值的位置
	int prev = begin;
	//下一个小于基准值的位置
	int cur = begin + 1;
	int key = arr[begin];
	while (cur <= end)
	{
		//当cur走到下一个小于基准值的位置,判断prev和cur是否连续
		if (arr[cur] < key && ++prev != cur)
		{
			//不连续,交换数据:prev, cur
			Swap(arr, prev, cur);
		}
		++cur;
	}
	Swap(arr, begin, prev);
	return prev;
}

void quickSort(int* arr, int begin, int end)
{
	if (begin >= end)
		return;
	//div:一次划分后,基准值位置
	int div = partion3(arr, begin, end);
	//左右两部分进行快速排序
	//[begin,div-1]
	//[div+1,end]
	quickSort(arr, begin, div - 1);
	quickSort(arr, div + 1, end);
}

4. 归并排序:

归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
在这里插入图片描述
归并:合并有序的子序列,产生更大的有序的子序列
分解:把数据按照位置,均衡的分成两个子序列,一直到子序列中只有一个数据
合并:相邻有序的子序列进行合并
排序的过程中:需要O(n)的辅助空间

//*********************************归并排序**************************
//时间复杂度:O(nlog(n))
//空间复杂度O(n)
//稳定性:稳定

//相邻子序列合并: begin end , end+1  end2
//begin      mid       end2
void merge(int* arr, int begin, int mid, int end, int* tmp)
{
	//递增
	//子区间:[begin, mid]    [mid+1, end]
	int begin1 = begin;
	int end1 = mid;
	int begin2 = mid + 1;
	int end2 = end;

	//辅助空间的起始位置
	int idx = begin;

	//合并有序序列
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (arr[begin1] <= arr[begin2])
			tmp[idx++] = arr[begin1++];
		else
			tmp[idx++] = arr[begin2++];
	}
	//判断是否有未合并的元素
	if (begin1 <= end1)
		memcpy(tmp + idx, arr + begin1, sizeof(int) * (end1 - begin1 + 1));
	if (begin2 <= end2)
		memcpy(tmp + idx, arr + begin2, sizeof(int) * (end2 - begin2 + 1));

	//合并之后的序列拷贝到原始数组的对应区间
	memcpy(arr + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
//归并方法实现(递归法)
void _mergeSort(int* arr, int begin, int end, int* tmp)
{
	if (begin >= end)
		return;
	int mid = begin + (end - begin) / 2;

	//首先划分子序列
	_mergeSort(arr, begin, mid, tmp);
	_mergeSort(arr, mid + 1, end, tmp);

	//合并两个有序的子序列[begin, mid]   [mid+1, end]
	merge(arr, begin, mid, end, tmp);
}

void mergeSort(int* arr, int n)
{
	//申请辅助空间
	int* tmp = (int*)malloc(sizeof(int) * n);
	_mergeSort(arr, 0, n - 1, tmp);
	free(tmp);
}

//归并方法实现(非递归方法)
void mergeSortNoR(int* arr, int n)
{
	//申请辅助空间
	int* tmp = (int*)malloc(sizeof(int) * n);
	int step = 1;
	while (step < n)
	{
		for (int idx = 0; idx < n; idx += 2 * step)
		{
			//找到两个待合并的子序列区间
			//[begin, mid]    [mid+1, end]
			int begin = idx;
			int mid = idx + step - 1;
			//判断是否存在第二个序列
			if (mid >= n - 1)
				//不存在第二个子序列,直接跳过
				continue;
			int end = idx + 2 * step - 1;
			//判断第二个子序列是否越界
			if (end > n)
				end = n - 1;
			merge(arr, begin, mid, end, tmp);
		}
		//更新步长
		step *= 2;
	}
}
//*********************************计数排序**************************

void countSort(int* arr, int n)
{
	//找到最大和最小值
	int max, min;
	min = max = arr[0];
	for (int i = 1; i < n; ++i)
	{
		if (arr[i] > max)
			max = arr[i];
		if (arr[i] < max)
			min = arr[i];
	}
	//计算范围
	int range = max - min + 1;
	//创建一个计数数组,初始化为0
	int* countArr = (int*)calloc(range, sizeof(int));

	//计数
	for (int i = 0; i < n; ++i)
	{
		countArr[arr[i] - min]++;
	}
	//遍历计数数组,排序
	int idx = 0;
	for (int i = 0; i < range; ++i)
	{
		while (countArr[i]--)
		{
			arr[idx++] = i + min;
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值