【常用排序算法归纳总结】

1.选择排序

       选择排序(Selection sort)是一种简单直观的排序算法。 它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。 以此类推,直到全部待排序的数据元素的个数为零。 选择排序是不稳定的排序方法。

选择排序的原理:

  1. 在 [L ... N-1] 范围内找出最小项目 X 的位置,

  2. 用第 L 项交换X,

  3. 将下限 L 增加1并重复步骤1直到 L = N-2

动画演示:

 

 排序过程:

  1. 从原序列中找到最小值,与数组第一个元素交换;
  2. 除第一个元素外,从剩下未排序的序列中找到最小值,与数组第二个元素交换;
  3. 共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}。

  1. 首先拿起第一张牌, 手上有 {2}。

  2. 拿起第二张牌 4, 把 4 insert 到手上的牌 {2}, 得到 {2 ,4}。

  3. 拿起第三张牌 5, 把 5 insert 到手上的牌 {2,4 }, 得到 {2 ,4,5}。

  4. 以此类推。

    插入排序由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]。

  算法步骤:

  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;

  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置;

  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;

  4. 重复步骤 3 直到某一指针达到序列尾;

  5. 将另一序列剩下的所有元素直接复制到合并序列尾。

举例:

现在我有两个已经排好顺序的数组: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);
}

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-Gaojs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值