1.选择排序
选择排序(Selection sort)是一种简单直观的排序算法。 它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。 以此类推,直到全部待排序的数据元素的个数为零。 选择排序是不稳定的排序方法。
选择排序的原理:
-
在 [L ... N-1] 范围内找出最小项目 X 的位置,
-
用第 L 项交换X,
-
将下限 L 增加1并重复步骤1直到 L = N-2。
动画演示:
排序过程:
- 从原序列中找到最小值,与数组第一个元素交换;
- 除第一个元素外,从剩下未排序的序列中找到最小值,与数组第二个元素交换;
- 共N-1趟,每趟都找到未排序的最小值,放到已排序的序列后面。
代码:
#include<iostream>
using namespace std;
int main() {
int n;
cin >> n;
int nums[n];
for (int i = 0; i < n; i++) cin >> nums[i];
for (int i = 0; i < n; i++) {
int index = i;
for (int j = index + 1; j < n; j++) {
if (nums[j] < nums[index]) index = j; // 选择排序的核心部分
}
swap(nums[index],nums[i]);
}
}
2.冒泡排序
冒泡排序(Bubble Sort):是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
核心:每次比较两个相邻的元素,如果它们的顺序错误就把它们交换过来。
冒泡排序的原理:
-
如果元素大小关系不正确,交换这两个数(在本例中为a> b),
-
比较一对相邻元素(a,b),
-
重复步骤1和2,直到我们到达数组的末尾(最后一对是第(N-2)和(N-1)项,因为我们的数组从零开始)
-
到目前为止,最大的元素将在最后的位置。 然后我们将N减少1,并重复步骤1,直到N = 1。
动画演示:
代码:
#include<iostream>
using namespace std;
void bubble_sort(int nums[], int n) {
for (int i = 0; i < n - 1; i++) { // n个数排序,只需要n-1趟即可
for (int j = 0; j < n - i - 1; j++) {
if (nums[j] > nums[j+1]) swap(nums[j],nums[j+1]);
// 冒泡排序的核心部分
}
}
}
int main() {
int n;
cin >> n;
int nums[n];
for (int i = 0; i < n; i++) cin >> nums[i];
bubble_sort(nums, n);
for (int i = 0; i < n; i++) cout << nums[i] << " ";// 输出结果
}
3.插入排序
插入排序(Insertion sort):一般也被称为直接插入排序。对于少量元素的排序,它是一个有效的算法 。插入排序是一种最简单的排序方法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增1的有序表。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动
算法思想:通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
插入排序就像许多人排序一手扑克牌。开始时,我们的左手为空并且桌子上的牌面向下。然后我们每次从桌子上拿走一张牌并将它插入左手中正确的位置。为了找到一张牌的正确位置,我们从右到左将它与已在手中的每张牌进行比较。如下图,拿在左手上的牌总是排序好的,原来这些牌是桌子上牌堆中顶部的牌。
-
举例:
Input: {2, 4, 5, 10, 7, 6, 1, 8}。
-
首先拿起第一张牌, 手上有 {2}。
-
拿起第二张牌 4, 把 4 insert 到手上的牌 {2}, 得到 {2 ,4}。
-
拿起第三张牌 5, 把 5 insert 到手上的牌 {2,4 }, 得到 {2 ,4,5}。
-
以此类推。
插入排序由N-1趟排序组成。对于p=1到N-1趟排序后,插入排序保证从位置0到位置p上的元素为已排序状态。即插入排序利用了从位置0到p-1位置上已经有序的条件,将位置p上的元素向前查找适当的位置插入此元素。
动画演示:
代码:
#include<iostream>
using namespace std;
void Insert_sort(int nums[], int n) {
for (int i = 1; i < n; i++) {
int key = nums[i]; // 将nums[i]插入到已经排好序的[0,i-1]系列当中
int j = i - 1;
while (j >= 0 && nums[j] > key) {
nums[j+1] = nums[j];
j = j - 1;
}
nums[j+1] = key;
}
}
int main() {
int n;
cin >> n;
int nums[n];
for (int i = 0; i < n; i++) cin >> nums[i];
Insert_sort(nums, n);
for (int i = 0; i < n; i++) cout << nums[i] << " ";
}
4.快速排序
快速排序(quick sort):是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。
排序过程:
(1)首先设定一个分界值,通过该分界值将数组分成左右两部分。
(2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于分界值,而右边部分中各元素都大于或等于分界值。
(3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
(4)重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。
举例:
排序【6,1,2,7,9,3,4,5,10,8】
首先以第一个数6作为基准数,先从右往左找到一个小于6的数,再从左往右找到一个大于6的数,然后交换它们。这里可以用两个变量i和j,分别指向序列的最左边和最右边。
交换i和j所指向的元素的值。交换之后的序列为:
6,1,2,5,9,3,4,7,10,8
到此,第一次交换结束,接下来j继续向左移动(每次必须是j先移动),j到4之后停了下来。i也继续向右移动,i到9之后停了下来。
此时再次进行交换,交换之后的序列如下:
6,1,2,5,4,3,9,7,10,8
j和i依次移动,直到二者相遇。
二者相遇后与基数6交换,此时6左边的数都小于等于6,6右边的数都大于等于6。
3,1,2,5,4,6,9,7,10,8
之后通过递归进行6左边的数的排序已经6右边的数的排序。
动画演示:
代码:
#include<iostream>
#include<vector>
using namespace std;
class Solution {
private:
vector<int> nums;
public:
void quick_sort(int left, int right) {
if (left > right) return;
int i = left, j = right, temp = nums[left];// temp 中存的是基准数
while(i != j){
while(nums[j] >= temp && i < j) j--;// 顺序很重要,要先从右往左找
while (nums[i] <= temp && i < j) i++;
if (i < j) swap(nums[i],nums[j]);
}
swap(nums[left],nums[i]);
this->quick_sort(left,i-1);// 继续处理左边的
this->quick_sort(i+1,right);// 继续处理右边的
}
void value(vector<int>& num) {
nums = num;
}
void print() {
for (int i = 0; i < nums.size(); i++) {
cout << nums[i] << " ";
}
cout << endl;
}
};
int main() {
Solution Q;
int n;
cin >> n;
vector<int> num(n);
for (int i = 0; i < n; i++) cin >> num[i];
Q.value(num);
Q.quick_sort(0,num.size()-1);
Q.print();
}
5.归并排序
归并排序(Merge sort):是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
过程:
比较a[i]和b[j]的大小,若a[i]≤b[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1;否则将第二个有序表中的元素b[j]复制到r[k]中,并令j和k分别加上1,如此循环下去,直到其中一个有序表取完,然后再将另一个有序表中剩余的元素复制到r中从下标k到下标t的单元。归并排序的算法我们通常用递归实现,先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间[s,t]。
算法步骤:
-
申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
-
设定两个指针,最初位置分别为两个已经排序序列的起始位置;
-
比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
-
重复步骤 3 直到某一指针达到序列尾;
-
将另一序列剩下的所有元素直接复制到合并序列尾。
举例:
现在我有两个已经排好顺序的数组:int[] arr1 = {2, 7, 8}
和int[] arr2 = {1, 4, 9}
,我还有一个大数组int[] arr
来装载它们
1. 将两个数组的值进行比较,谁的值比较小,谁就放入大数组中!
首先,拿出
arr1[0]
和arr2[0]
进行比较,显然是arr2[0]
比较小,因此将arr2[0]
放入大数组中,同时arr2
指针往后一格所以,现在目前为止
arr = {1}
2. 拿
arr1[0]
和arr2[1]
进行比较,显然是arr1[0]
比较小,将arr1[0]
放入大数组中,同时arr1
指针往后一格所以,现在目前为止
arr = {1,2}
3.
随后,拿
arr1[1]
和arr2[1]
进行比较,显然是arr2[1]
比较小,将arr2[1]
放入大数组中,同时arr2
指针往后一格所以,现在目前为止
arr = {1,2,4}
........
遍历到最后,我们会将两个已排好序的数组变成一个已排好序的数组
arr = {1,2,4,7,8,9}
归并排序的前提是需要两个已经排好顺序的数组,那往往不会有两个已经排好顺序的数组给我们,此时,我们就得用到分治的思想了:
当我们要做归并的时候就以arr[3]
也就元素为1的那个地方分开。是然后用一个指针L
指向arr[0]
,一个指针M
指向arr[3]
,用一个指针R
指向arr[5]
(数组最后一位)。有了指针的帮助,我们就可以将这个数组切割成是两个有序的数组了(操作的方式就可以和上面一样了)
如果给出的是杂乱无章的一个数组,现在还是达不到要求。比如给出的是这样一个数组:int[] arrays = {9, 2, 5, 1, 3, 2, 9, 5, 2, 1, 8};
我们也可以将int[] arr = {2, 7, 8, 1, 4, 9};
数组分隔成一份一份的,arr[0]
它是一个有序的"数组",arr[1]
它也是一个有序的"数组",利用指针(L,M,R)又可以像操作两个数组一样进行排序。最终合成{2,7}
.......再不断拆分合并,最后又回到了我们的arr = {1,2,4,7,8,9}。
动画演示:
代码:
#include<iostream>
#include<vector>
using namespace std;
class Solution {
public:
void Merge(vector<int>&nums, vector<int>temp, int left, int mid, int right) {
int i = left; // i是第一段序列的下标
int j = mid + 1; // j是第二段序列的下标
int k = 0;// k是临时存放合并序列的下标
// 扫描第一段和第二段序列,直到有一个扫描结束
while (i <= mid && j <= right) {
// 判断第一段和第二段取出的数哪个更小,将其存入合并序列,并继续向下扫描
if (nums[i] <= nums[j]) temp[k++] = nums[i++];
else temp[k++] = nums[j++];
}
// 若第一段序列还没扫描完,将其全部复制到合并序列
while (i <= mid) temp[k++] = nums[i++];
// 若第二段序列还没扫描完,将其全部复制到合并序列
while (j <= right) temp[k++] = nums[j++];
k = 0;
// 将合并序列复制到原始序列中
while (left <= right) nums[left++] = temp[k++];
}
void merge_sort(vector<int>& nums, vector<int> temp, int left, int right) {
if (left < right) {
int mid = (left + right) >> 1;
this->merge_sort(nums,temp,0,mid);
this->merge_sort(nums,temp,mid+1,right);
this->Merge(nums,temp,left,mid,right);
}
}
void constructor(vector<int>& nums) {
// 在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
vector<int> temp(nums.size());
merge_sort(nums,temp,0,nums.size()-1);
}
void print(vector<int>& nums) {
for (int i = 0; i < nums.size(); i++) {
cout << nums[i] << " ";
}
cout << endl;
}
};
int main() {
int n;
cin >> n;
vector<int> nums(n);
for (int i = 0; i < n; i++) {
cin >> nums[i];
}
Solution Q;
Q.constructor(nums);
Q.print(nums);
}