常见排序方法总结

数据结构中常见排序方法主要有以下几种:

插入排序: ① 直接插入排序 ② 希尔排序

选择排序: ① 选择排序 ② 堆排序

交换排序: ① 冒泡排序 ② 快速排序

归并排序

1. 直接插入排序

假设第一个数据有序, 将待插入数据从后向前依次比较.

代码如下:

void InsertSort(int* array, int n) {
	for (int i = 0; i < n - 1; i++) {		
		//新数据插入
		//end表示有序序列的最后一个位置
		int end = i;
		//key表示待插入数据
		int key = array[end + 1];
		//找到第一个小于等于key的位置
		while (end >= 0 && array[end] > key) {
			//当前数据向后移动
			array[end + 1] = array[end];
			end--;
		}
		array[end + 1] = key;
	}
}

插入排序特性:

时间复杂度: 最坏O(n^2)   最好O(n)  平均O(n^2)
空间复杂度: O(1)
稳定性: 稳定
数据敏感度: 敏感

2. 希尔排序

先进行多轮预排序, 再进行普通插入排序

代码如下:

void ShellSort(int* array, int n) {
	//gap表示步长
	int gap = n;
	//多轮插入排序
	while (gap > 1) {
		gap = gap / 2;   //或者 gap = gap / 3 + 1;
		//一轮插入排序
		for (int i = 0; i < n - gap; i++) {
			//通过gap进行逻辑分组
			//组内各自进行插入排序
			//不同族元素交替排序
			int end = i;
			int key = array[end + gap];
			while (end >= 0 && array[end] > key) {
				array[end + gap] = array[end];
				end -= gap;
			}
			array[end + gap] = key;
		}
	}
}

希尔排序特性:

时间复杂度: 最坏O(n^1.3)   最好O(n^1.3)  平均O(n)
空间复杂度: O(1)
稳定性: 不稳定---->预排序可能导致相对位置发生变化
数据敏感度: 敏感

3. 选择排序

每一次从待排序的数据中选取最大或最小值, 放在序列的起始位置, 直到全部元素排完.

代码如下:

void SelectSort(int* array, int n) {
	for (int i = 0; i < n; i++) {
		//start表示未排序数据的最左边
		int start = i;
		//最小值的位置
		int min = start;
		//从未排序数据中找最小值
		for (int j = start + 1; j < n; j++) {
			if (array[j] < array[min]) {
				min = j;
			}
		}
		Swap(array, start, min);
	}
}

选择排序特性:

时间复杂度: 最坏O(n^2)   最好O(n^2)  平均O(n^2)
空间复杂度: O(1)
稳定性: 稳定
数据敏感度: 不敏感

选择排序还有第二种优化方案: 同时选取最大值和最小值进行排序, 可以提高时间效率

代码如下:

void SelectSort2(int* array, int n) {
	int begin = 0;
	int end = n - 1;
	while (begin < end) {
		//每一次选择最大值和最小值
		int min = begin;
		int max = begin;
		for (int i = begin + 1; i <= end; i++) {
			if (array[i] > array[max]) {
				max = i;
			}
			if (array[i] < array[min]) {
				min = i;
			}
		}
			//最小值放在begin
			Swap(array, begin, min);
			//如果最大值位置发生变化, 需要更新
			if (max == begin) {
				max = min;
			}
			//最大值放在end
			Swap(array, end, max);

			begin++;
			end--;
	}
}

4. 堆排序

是选择排序的一种, 排升序需要建大堆, 排降序需要建小堆.

代码如下:

void AdjustDwon(int* array, int n, int parent) {
	int child = 2 * parent + 1;
	while (child < n) {
		if (child + 1 < n && array[child + 1] > array[child]) {
			child++;
		}
		if (array[child] > array[parent]) {
			Swap(array, child, parent);
			parent = child;
			child = 2 * parent + 1;
		}
		else {
			break;
		}
	}
}
void HeapSort(int* array, int n) {
	//建堆
	for (int i = (n - 2) / 2; i >= 0; i--) {
		AdjustDwon(array, n, i);
	}
	//循环删除
	while (n) {
		Swap(array, 0, n - 1);
		n--;
		AdjustDwon(array, n, 0);
	}
}

堆排序特性:

时间复杂度: 最坏O(nlogn)   最好O(nlogn)  平均O(nlogn)
空间复杂度: O(1)
稳定性: 不稳定
数据敏感度: 不敏感

5. 冒泡排序

相邻元素进行比较, 大的向后移动

代码如下:

void BubbleSort(int* array, int n) {
	while (n) {
		//一轮冒泡排序
		for (int i = 0; i < n - 1; i++) {
			//相邻元素进行比较, 大的向后移动
			if (array[i] > array[i + 1]) {
				Swap(array, i, i + 1);
			}
		}
		n--;
	}
}

冒泡排序特性:

时间复杂度: 最坏O(n^2)   最好O(n)  平均O(n^2)
空间复杂度: O(1)
稳定性: 稳定
数据敏感度: 敏感

6. 快速排序

三种常见的快速排序方法为: hoare法,  挖坑法,  前后指针法

(1) hoare法

步骤: ① 从待划分区间选取一个基准值;

         ② 从待划分区间从后向前找第一个小于基准值的数据;

         ③ 从前向后找第一个大于基准值的数据;

         ④ 将②③找到的数据交换, 循环执行②③④;

         ⑤ 当查找到相遇位置时, 把基准值和相遇位置进行交换.

tps. ②③顺序不能换, 否则会导致第⑤步进行交换时出问题.

代码如下:

int PartSort1(int* array, int left, int right) {
	//选择基准值
	int key = array[left];
	int start = left;
	//划分
	while (left < right) {
		//从后往前找第一个小于key的位置
		while (left < right && array[right] >= key) {
			right--;
		}
		//从前向后找第一个大于key的位置
		while (left < right && array[left] <= key) {
			left++;
		}
		//交换left, right
		Swap(array, left, right);
	}
	//key和相遇位置数据进行交换
	Swap(array, start, left);
	//返回基准值位置
	return left;
}

(2) 挖坑法

步骤: ① 选取基准值, 第一个坑为基准值位置;

         ② 从后向前找第一个小于基准值的数据, 去填上一次的坑;

         ③ 从前向后找第一个大于基准值的数据, 去填上一次的坑, 循环执行②③;

         ④ 相遇位置, 将基准值填入坑内

代码如下:

int PartSort2(int* array, int left, int right) {
	//选取基准值
	int key = array[left];
	while (left < right) {
		//从后往前找第一个小于key的位置
		while (left < right && array[right] >= key) {
			right--;
		}
		//填坑:left位置的坑
		array[left] = array[right];
		//从前向后找第一个大于key的位置
		while (left < right && array[left] <= key) {
			left++;
		}
		//填坑:right位置的坑
		array[right] = array[left];
	}
	//相遇位置, 最后一个坑, 填入key
	array[left] = key;
	//返回基准值的位置
	return left;
}

(3) 前后指针法

设定两个指针: 前指针和后指针

                        cur表示下一个小于基准值的位置

                        prev表示最后一个小于基准值的位置

判断两个指针之间是否有大于基准值的数据--------------如果前后指针连续: 表示中间没有大于基准值的数据

                                                                                     如果前后指针不连续: 表示中间有大于基准值得数据

如果不连续, 则将大数据和新发现的小数据进行交换, 最终实现基准值左边都是小于基准值的, 右边都是大于基准值的

代码如下: 

int PartSort3(int* array, int left, int right) {
	//选取基准值
	int key = array[left];
	//prev: 最后一个小于基准值的位置
	int prev = left;
	//cur: 新发现的下一个小于基准值的位置
	int cur = left + 1;
	while (cur <= right) {
		//找到下一个小于基准值的位置并且判断前后指针是否连续
		if (array[cur] < key && prev + 1 != cur) {
			//下一个大于基准值的位置
			prev++;
			//小于的和大于的交换
			//大数据向后移动, 小数据向前移动
			Swap(array, prev, cur);
		}
		cur++;
	}
	Swap(array, left, prev);
	return prev;
}

快速排序的特性:

时间复杂度: 最坏O(n^2)-->基本不会出现   最好O(nlogn)  平均O(nlogn)
空间复杂度: O(logn)
稳定性: 不稳定
数据敏感度: 敏感

快速排序的优化: 

三数取中法确定基准值: 可以把最坏情况变成最理想情况----> 均衡划分

代码如下:

int getMid(int* array, int begin, int end) {
	int mid = begin + (end - begin) / 2;
	//begin, mid, end中选取中间值位置
	if (array[begin] < array[mid]) {
		//begin < mid;
		if (array[mid] < array[end]) {
			return mid;
		}
		else {
			// begin < mid, end <= mid
			if (array[begin] > array[end]) {
				return begin;
			}
			else {
				return end;
			}
		}
	}
	else {
		//begin >= mid;
		if (array[mid] > array[end]) {
			return mid;
		}
		else {
			//begin >= mid, end >= mid;
			if (array[begin] < array[end]) {
				return begin;
			}
			else {
				return end;
			}
		}
	}
}

快速排序的非递归实现:

栈实现非递归: 区间入栈----每次获取栈顶区间, 进行划分----划分之后的小区间继续入栈----直到栈为空结束

代码如下:

//栈实现非递归: 保存待划分区间
void QuickSortNonR(int* array, int n) {
	Stack st;
	stackInit(&st, 10);
	//起始区间入栈: 先右后左
	if (n > 1) {
		stackPush(&st, n - 1);
		stackPush(&st, 0);
	}
	//遍历栈, 划分栈中的每个区间
	while (stackEmpty(&st) != 1) {
		//获取栈顶区间
		int begin = stackTop(&st);
		stackPop(&st);
		int end = stackTop(&st);
		stackPop(&st);
		//划分
		int keyPos = PartSort3(array, begin, end);
		//子区间入栈: 先入右区间
		//右: keyPos + 1, end
		if (keyPos + 1 < end) {
			stackPush(&st, end);
			stackPush(&st, keyPos + 1);
		}
		//左: begin, keyPos - 1
		if (begin < keyPos - 1) {
			stackPush(&st, keyPos - 1);
			stackPush(&st, begin);
		}
	}
}

队列实现快排非递归:

//队列实现: 快排非递归
void QuickSortNonR(int* array, int n) {
	Queue q;
	queueInit(&q);
	
	if (n > 1) {
		//先左后右
		queuePush(&q, 0);
		queuePush(&q, n - 1);
	}
	while (queueEmpty(&q) != 1) {
		//获取队头区间
		int begin = queueFront(&q);
		queuePop(&q);
		int end = queueFront(&q);
		queuePop(&q);
		//划分
		int keyPos = PartSort3(array, begin, end);
		//子区间入队
		if (begin < keyPos - 1) {
			queuePush(&q, begin);
			queuePush(&q, keyPos - 1);
		}
		if (keyPos + 1 < end) {
			queuePush(&q, keyPos + 1);
			queuePush(&q, end);
		}
	}
}

7. 归并排序

合并有序的子序列, 使其变成一个更大的有序子序列.

合并的前提是:  子序列本身有序

开始的子序列: 只包含一个元素的子序列本身有序

合并过程:  相邻的有序子序列进行合并

tips. 不能进行原地合并, 会覆盖元素 ---------> 要借助辅助空间

代码如下:

//合并:  需要知道两个有序子序列的空间: [begin, mid] [mid + 1, end]
void Merge(int* array, int begin, int mid, int end, int* tmp) {
	int begin1 = begin, end1 = mid, begin2 = mid + 1, end2 = end;
	int idx = begin;
	//合并
	while (begin1 <= end1 && begin2 <= end2) {
		if (array[begin1] <= array[begin2]) {
			tmp[idx++] = array[begin++];
		}
		else {
			tmp[idx++] = array[begin2++];
		}
	}
	//查看是否有剩余元素
	if (begin1 <= end1) {
		memcpy(tmp + idx, array + begin1, sizeof(int) * (end1 - begin1 + 1));
	}
	if (begin2 <= end2) {
		memcpy(tmp + idx, array + begin2, sizeof(int) * (end2 - begin2 + 1));
	}
	//拷贝到原始空间
	memcpy(array + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
void MergeSortR(int* array, int begin, int end, int* tmp) {
	if (begin >= end) {
		return;
	}
	int mid = begin + (end - begin) / 2;
	//首先保证子区间有序, 子区间先进行排序
	MergeSortR(array, begin, mid, tmp);
	MergeSortR(array, mid + 1, end, tmp);
	//合并有序子区间
	Merge(array, begin, mid, end, tmp);
}

void MergeSort(int* array, int n) {
	int* tmp = (int*)malloc(sizeof(int) * n);
	MergeSortR(array, 0, n - 1, tmp);
	free(tmp);
}

归并排序非递归实现, 代码如下:

void MergeSortNonR(int* array, int n) {
	int* tmp = (int*)malloc(sizeof(int) * n);
	//待合并区间的元素个数
	int k = 1;
	//多轮的归并
	while (k < n) {
		k *= 2;
		//一轮归并
		for (int i = 0; i < n; i += 2 * k) {
			//[begin, mid] [mid + 1, end]
			int begin = i;
			int mid = i + k - 1;
			//判断mid是否越界
			if (mid >= n - 1) {
				continue;
			}
			int end = i + 2 * k - 1;
			//判断end是否越界
			if (end >= n) {
				end = n - 1;
			}
			Merge(array, begin, mid, end, tmp);
		}
	}
}

归并排序特性:

时间复杂度: 最坏O(nlogn)   最好O(nlogn)  平均O(nlogn)
空间复杂度: O(n)
稳定性: 稳定
数据敏感度: 不敏感

8. 计数排序 

代码如下:

void CountSort(int* array, int n) {
	//统计范围
	int min = array[0], max = array[0];
	for (int i = 1; i < n; i++) {
		if (array[i] > max) {
			max = array[i];
		}
		if (array[i] < min) {
			min = array[i];
		}
	}
	int range = max - min + 1;
	//开辅助空间, 进行计数
	int* countArr = (int*)malloc(sizeof(int) * range);
	//初始化为0
	memset(countArr, 0, sizeof(int) * range);
	//统计次数
	for (int i = 0; i < n; i++) {
		countArr[array[i] - min]++;
	}
	//恢复数据, 遍历计数数组
	int idx = 0;
	for (int i = 0; i < range; i++) {
		while (countArr[i]--) {
			array[idx++] = i + min;
		}
	}
	free(countArr);
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值