【数据结构与算法】排序

排序

若相同关键字元素间的位置关系,排序前与排序后保持一致,称此排序方法是稳定的,不能保持一致的排序方法则称为不稳定的。内排序:待排序列完全存放在内存中。外排序:排序过程中还需访问外存,待排序列不能完全放入内存。本文只介绍内排序。

一、插入排序

将待排序的数据按其关键字大小依次插入到前面已排序的数据序列的适当位置上。

1. 直接插入排序

方法:用顺序查找的方法找出待插入元素应该插入的位置。对于有n个数据元素的待排序序列,直接插入排序要进行n-1趟,待排序序列用数组和链表存放均可。

#include <iostream>
#include <vector>
using namespace std;

void InsertSort(vector<int>& arr)
{
	int n = arr.size();
	for (int i = 1; i < n; i++)
	{
		int temp = arr[i];
		int j;
		for (j = i; j > 0 && arr[j - 1] > temp; j--)
		{
			arr[j] = arr[j - 1];
		}
		arr[j] = temp;
	}
}

int main()
{
	vector<int> num = { 53,27,36,15,69,42 };
	InsertSort(num);
	for (auto v : num)
	{
		cout << v << " ";
	}	// 15 27 36 42 53 69
	cout << endl;
	system("pause");
	return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 二分查找排序

方法:用二分查找方法找出找出待插入元素应该插入的位置。对于有n个数据元素的待排序序列,二分插入排序要进行n-1趟,是一种稳定的排序算法,待排序序列必须存放于数组。

#include <iostream>
#include <vector>
using namespace std;

void BinsertSort(vector<int>& arr)
{
	int i, j, temp;
	int low, high, mid;
	int n = arr.size();
	// 从第二个元素开始
	for (i = 1; i < n; i++)
	{
		// 第 i 个元素比前面已经有序的 i-1 个元素中最大的数要小
		if (arr[i - 1] > arr[i])
		{
			temp = arr[i];
			low = 0;
			high = i - 1;
			// 二分查找
			while (low <= high)
			{
				mid = (low + high) / 2;
				if (temp < arr[mid])
				{
					high = mid - 1;
				}
				else
				{
					low = mid + 1;
				}
			}
			for (j = i - 1; j >= high + 1; j--)
			{
				arr[j + 1] = arr[j];
			}
			// 最后插入的位置是 low 或 high + 1
			arr[high + 1] = temp;
		}
	}
}

int main()
{
	vector<int> num = { 53,27,36,15,69,42 };
	BinsertSort(num);
	for (auto v : num)
	{
		cout << v << " ";
	}	// 15 27 36 42 53 69
	cout << endl;
	system("pause");
	return 0;
}

二分插入排序第五趟:
在这里插入图片描述
在这里插入图片描述

3. 希尔排序

方法:希尔排序将大问题分割为小问题 , 每一小问题采用直接插入排序,所有小问题的完成使得整个待排序的数据基本有序时,再一起进行插入排序。首先设定正整数增量d1(d1< n),将待排序的数据分成d1组,各组内进行直接插入排序。然后取增量 d2 < d1,重复上述分组和排序工作,直至取 di = 1,即所有数据元素放在一个组内进行排序。

#include <iostream>
#include <vector>
using namespace std;

// 增量为d的一趟希尔排序
void ShellSort(vector<int>& arr, int d)
{
	int n = arr.size();
	int i, j, temp;
	// 从第 d+1 个元素开始
	for (i = d; i < n; i++)
	{
		// 第 i 个元素比前面已经有序的元素中最大的数要小 
		if (arr[i - d] > arr[i])
		{
			temp = arr[i];
			for (j = i - d; j >= 0 && (arr[j] > temp); j = j - d)
			{
				arr[j + d] = arr[j];
			}
			arr[j + d] = temp;
		}
	}
}

int main()
{
	vector<int> num = { 39,80,76,41,13,29,50,78,30,11,100,7 };
	// 增量d = 4
	ShellSort(num, 4);
	for (auto v : num)
	{
		cout << v << " ";
	}	// 13 11 50 7 30 29 76 41 39 80 100 78
	cout << endl;

	// 增量d = 2
	ShellSort(num, 2);
	for (auto v : num)
	{
		cout << v << " ";
	}	// 13 7 30 11 39 29 50 41 76 78 100 80
	cout << endl;

	// 增量d = 1
	ShellSort(num, 1);
	for (auto v : num)
	{
		cout << v << " ";
	}	// 7 11 13 29 30 39 41 50 76 78 80 100
	cout << endl;

	system("pause");
	return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
希尔排序是不稳定的排序算法:
在这里插入图片描述

二、交换排序

两两比较待排序数据的关键字的值,并交换那些不满足顺序要求的一对,直到全部满足顺序要求为止。

1. 冒泡排序

方法:将待排序的数据元素的关键字顺次两两比较,若为逆序则将两个数据元素交换。将序列照此方法从头到尾处理一遍称作一趟冒泡排序,它将关键字值最大的数据元素交换到排序的最终位置,若某一趟冒泡排序没发生任何数据元素的交换或做完 n-1 趟,则排序过程结束。

#include <iostream>
#include <vector>
using namespace std;

void BubbleSort(vector<int>& arr)
{
	int temp;
	bool flag = true;
	int n = arr.size();
	for (int i = 0; (i < n - 1) && flag; i++)
	{
		flag = false;
		for (int j = 0; j < n - i - 1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				flag = true;
				temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}

int main()
{
	vector<int> num = { 25,49,56,11,65,41,36,78 };
	BubbleSort(num);
	for (auto v : num)
	{
		cout << v << " ";
	}	// 11 25 36 41 49 56 65 78
	cout << endl;
	system("pause");
	return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 快速排序

方法:在待排序的n个数据元素中任取一个数据元素(通常取第一个)为基准,用交换的方法将所有数据元素分成三部分,所有键值比它小的安置在一部分,所有键值比它大的安置在另一部分,并把该数据元素放在这两部分的中间,这也是该数据元素排序后最终位置,这个过程称为一趟快速排序。一趟快速排序,将待排序序列划分为两个子序列。然后分别对所划分的两个子序列重复上述过程,一直重复到每部分只有一个数据元素为止。

#include <iostream>
#include <vector>
using namespace std;

int partition(vector<int>& arr, int low, int high)
{
	// 基准
	int temp = arr[low];
	while (low < high)
	{
		// 从 high 所指的位置向左搜索
		// 与基准相等的元素放在基准的右侧
		while (low < high && arr[high] >= temp)
		{
			high--;
		}
		if (low < high)
		{
			arr[low] = arr[high];
			low++;
		}
		// 从 low 所指的位置向右搜索
		while (low < high && arr[low] < temp)
		{
			low++;
		}
		if (low < high)
		{
			arr[high] = arr[low];
			high--;
		}
	}
	// 基准的最终位置
	arr[low] = temp;
	return low;
}

void QuickSort(vector<int>& arr, int low, int high)
{
	int temp;
	if (low < high)
	{
		temp = partition(arr, low, high);
		QuickSort(arr, low, temp - 1);
		QuickSort(arr, temp + 1, high);
	}
}

int main()
{
	vector<int> num = { 49 ,13, 38, 65, 67, 76 ,13, 50 };
	QuickSort(num, 0, num.size() - 1);
	for (auto v : num)
	{
		cout << v << " ";
	}	// 13 13 38 49 50 65 67 76
	cout << endl;
	system("pause");
	return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
快速排序是不稳定的排序算法:
在这里插入图片描述

三、选择排序

每次从待排序的数据元素中选取关键字值最小的数据元素,顺序放在已排序的数据元素的最前面,直到全部排完。

1. 简单选择排序

每次从待排序的数据元素中选取关键字值最小的数据元素,顺序放在已排序的数据元素的最前面,直到全部排完。对于有n个数据元素的待排序序列,直接插入排序要进行n-1趟,适用于待排序元素较少的情况。

#include <iostream>
#include <vector>
using namespace std;

void SelectSort(vector<int>& arr)
{
	int i, j, k, temp;
	int n = arr.size();
	// 进行 n-1 趟简单选择排序
	for (i = 0; i < n - 1; i++)
	{
		k = i;
		// 找出最小的元素
		for (j = i + 1; j < n; j++)
		{
			if (arr[j] < arr[k])
			{
				k = j;
			}
		}
		if (k != i)
		{
			// 交换
			temp = arr[i];
			arr[i] = arr[k];
			arr[k] = temp;
		}
	}
}

int main()
{
	vector<int> num = { 8,3,9,6,1 };
	SelectSort(num);
	for (auto v : num)
	{
		cout << v << " ";
	}	// 1 3 6 8 9
	cout << endl;
	system("pause");
	return 0;
}

在这里插入图片描述
简单选择排序是不稳定的排序算法:
在这里插入图片描述

2. 堆排序

堆用一个一维数组存放,按完全二叉树的层次编号顺序存放。小(大)顶堆解释为:完全二叉树中任一非叶结点的值均不大(小)于它左、右孩子结点的值。根结点是堆中的最小(大)值,适用于待排序元素较多的情况。
在这里插入图片描述

筛选法建堆

n个数据建大顶堆,从第n/2个数据开始进行调整(从下到上、从右至左找到第一个非叶结点开始进行调整),保证以该结点为根的子树为大顶堆。一个结点调整结束后,按照从下到上、从右至左的顺序找到下一个结点继续调整,直至根结点。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

堆排序过程

将关键字值最大的数据与尚未排序的最后一个数据交换存储位置,用剩下的数据元素重建堆(称为一次调整),便得到关键字值次大的数据元素。如此反复,直到全部数据排好序。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include <iostream>
#include <vector>
using namespace std;

// i:调整的索引
// n:数组长度
void adjust(vector<int>& arr, int i, int n)
{
	int temp = arr[i];
	// 保证有左孩子
	for (int k = 2 * i + 1; k < n; k = 2 * i + 1)
	{
		// 有右孩子,选择左右孩子更大的
		if (k + 1 < n && arr[k] < arr[k + 1])
		{
			k += 1;
		}
		// 与该结点比较
		if (temp >= arr[k])
		{
			break;
		}
		// “交换”
		arr[i] = arr[k];
		i = k;
	}
	arr[i] = temp;
}

void HeapSort(vector<int>& arr)
{
	int n = arr.size();
	// 初建大顶堆
	for (int i = n / 2 - 1; i >= 0; i--)
	{
		adjust(arr, i, n);
	}
	// n-1趟排序
	for (int k = n - 1; k > 0; k--)
	{
		int temp = arr[0];
		arr[0] = arr[k];
		arr[k] = temp;
		// 重新调整
		adjust(arr, 0, k);
	}
}

int main()
{
	vector<int> num = { 25,56,49,78,11,65,41,36 };
	HeapSort(num);
	for (auto v : num)
	{
		cout << v << " ";
	}	// 11 25 36 41 49 56 65 78
	cout << endl;
	system("pause");
	return 0;
}

四、归并排序

将待排序序列划分成若干有序子序列,将两个或两个以上的有序子序列 “合并” 为一个有序序列。自顶向下划分子序列:若待排序的序列包含多于一个数据元素,则将其一分为二划分为两个子序列。总共需进行log2n(向上取整)趟,一趟归并的时间复杂度为O(n)。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include <iostream>
#include <vector>
using namespace std;

void merge(vector<int>& arr, vector<int>& aux, int left, int right, int mid)
{
	// 复制
	for (int i = left; i <= right; i++)
	{
		aux[i] = arr[i];
	}
	int i = left;
	int j = mid + 1;
	for (int k = left; k <= right; k++)
	{
		// [left, mid] 排完
		if (i > mid)
		{
			arr[k] = aux[j];
			j++;
		}
		// [mid+1, right] 排完
		else if (j > right)
		{
			arr[k] = aux[i];
			i++;
		}
		else if (aux[i] < aux[j])
		{
			arr[k] = aux[i];
			i++;
		}
		else
		{
			arr[k] = aux[j];
			j++;
		}
	}
}

// [left, right]
void mergeSort(vector<int>& arr, vector<int>& aux, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	int mid = left + (right - left) / 2;
	mergeSort(arr, aux, left, mid);			// [left, mid]
	mergeSort(arr, aux, mid + 1, right);	// [mid+1, right]
	merge(arr, aux, left, right, mid);
}


void MergeSort(vector<int>& arr)
{
	// 辅助数组
	vector<int> aux = arr;
	int n = arr.size();
	mergeSort(arr, aux, 0, n - 1);
}


int main()
{
	vector<int> num = { 52,23,80,36,68,14 };
	MergeSort(num);
	for (auto v : num)
	{
		cout << v << " ";
	}	// 14 23 36 52 68 80
	cout << endl;
	system("pause");
	return 0;
}

五、基数排序

根据待排序数据元素关键字本身的性质进行排序,适合元素关键字值集合并不大的情况。

1. 桶排序

在这里插入图片描述
(1)首先按照面值对所有牌进行排序,然后按照花色再次对所有牌进行排序
第一次分配:依次查看52张牌,根据其面值将其放入对应的桶中。
第一次收集:从“2”号开始,将每个桶中的牌顺次倒出,52张牌现在的顺序是4张2,4张3,4张4,…,4张K,4张A。
在这里插入图片描述
第二次分配:依次查看第一次收集得到52张牌,根据花色将其放入对应的桶中。
第二次收集:从黑桃桶开始依次收集,将每个桶桶口封死,桶底打开,顺次倒出每一个桶中的牌,52张牌就排好序了:13张黑桃从2到A,13张红桃从2到A,13张梅花从2到A,13张方块从2到A。
在这里插入图片描述
(2)首先按照花色将所有牌分成四组。然后同花色再按照面值进行排序
第一次分配:依次查看52张牌,根据花色将其放入对应的桶中。
在这里插入图片描述
能否和前面的方法一样将所有桶依次收集在一起?
① 一起收集:从黑桃桶开始,将每个桶中的牌顺次倒出,52张牌现在的顺序是13张黑桃,13张红桃,13 张梅花,13张红桃。得不到正确的排序结果!
在这里插入图片描述
② 不一起收集。先收集黑桃桶,对收集的结果按照面值进行桶排序,再收集红桃桶,对收集的结果按照面值进行桶排序…

2. 基数排序

{278,109,063,930,589,184,505,269,008,083},采用低位优先基数排序。首先拆成3个关键字:个位、十位、百位,然后从个位开始进行以下三趟基数排序:首先按其“个位数”取值分配到“0, 1, …, 9”10个桶,之后按从0至9的顺序将它们“收集”在一起;然后按其“十位数”取值分配到“0, 1, …, 9”10个桶,之后按从0至9的顺序将它们 “收集”在一起;最后按其“百位数”重复一遍上述操作。
待排序记录以指针相连,构成一个链表;“分配”时,按当前“关键字位”的值,将记录分配到不同的“链队列”(桶)中,每个队列(桶)中记录的“关键字位”相同;“收集”时,按当前关键字位取值从小到大将各队列(桶)首尾相链成一个链表;对每个关键字位均重复2和3两步。

  • 时间复杂度为O(d*(n+rd))。其中分配为O(n),收集为O(rd)(rd为“基”),d为“分配-收集”的趟数
  • 空间复杂度为O(rd)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

总结

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
注:k为“桶”的个数。排序方式字段中,In-place代表占用常数内存,不占用额外内存。Out-place代表占用额外内存。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java提供了多种数据结构算法排序的实现。以下是一些常见的排序算法: 1. 冒泡排序(Bubble Sort):通过不断比较相邻元素并交换,将最大(或最小)的元素逐渐“冒泡”到最后(或最前)位置。时间复杂度为O(n^2)。 2. 选择排序(Selection Sort):每次选择未排序部分中最小(或最大)的元素,放到已排序部分的末尾(或开头)。时间复杂度为O(n^2)。 3. 插入排序(Insertion Sort):将未排序部分的元素逐个插入已排序部分的正确位置。时间复杂度为O(n^2)。 4. 快速排序(Quick Sort):通过选择一个基准元素,将表分为两个子表,其中一个子表的所有元素都小于(或大于)基准元素,然后递归地排序两个子表。时间复杂度平均情况为O(n log n)。 5. 归并排序(Merge Sort):将表拆分为多个子表,递归地对每个子表进行排序,然后合并这些有序子表以获得最终有序表。时间复杂度为O(n log n)。 6. 堆排序(Heap Sort):将待排序元素构建成一个最大(或最小)堆,然后逐个移除堆顶元素并将其放入已排序部分。时间复杂度为O(n log n)。 7. 希尔排序(Shell Sort):将表按照一定的间隔分组,并对每个分组进行插入排序,然后逐渐缩小间隔,直到间隔为1,最后再进行一次插入排序。时间复杂度取决于间隔序的选择。 Java中提供了Arrays类和Collections类来对数组和集合进行排序,可以使用它们的sort方法。另外,你也可以自己实现这些排序算法

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值