常见排序算法


一、排序分类

1.根据排序方式不同分

插入排序直接插入排序、折半插入排序、希尔排序
交换排序冒泡排序、快速排序
选择排序简单选择排序、堆排序
归并排序2-路归并排序
基数排序

2.根据自然性分

自然排序:输入数据越有序,排序速度越快
非自然能排序:不是自然排序的排序方法

3.根据辅助空间分

原地排序:辅助空间超过O(1)的排序方法
非原地排序:辅助空间超过O(1)的排序方法

4.根据操作方法分

比较排序插入排序、交换排序、选择排序、归并排序
基数排序不使用比较的方法,仅仅根据元素本身的取值确定有序位置

5.根据比较器的个数分

串行排序:单处理机(同一时刻比较一对元素)
并行排序:多处理机(同一时刻比较多对元素)

二、插入排序

在这里插入图片描述

1.直接插入排序

直接插入是一个一个元素对比,找到位置后,将元素插入,插入位置后面的元素向后移动

代码表示:

void test(vector<int>& nums)
{
	for(int i = 1; i < nums.size(); i++)
	{
		int temp = nums[i];
		for(int j = i - 1; j >= 0; j++)
		{
			if(nums[j] < nums[i])
			{
				nums[j+1] = temp;
				break;
			} 
			nums[j+1] = nums[j];
		}
	}
}

2.折半插入排序

和直接插入不同的是,折半插入排序不是一个一个元素对比,而是使用二分查找,找到插入位置。找到位置后,将元素插入,插入位置后面的元素向后移动

#include <iostream>

using namespace std;


void insertionSort(vector<int>& nums, int n) {
    for (int i = 1; i < nums.size(); ++i) {
        int aux = nums[i];
        int mid;
        int left = 0, right = i - 1;
        while (left <= right) {
            mid = left + (right - left) / 2;
            if (aux < nums[mid]) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        // left 为待插入的点
        for (int j = i; j > left; --j) {
            arr[j] = arr[j - 1];
        }
        arr[left] = aux;
    }
}


3.希尔排序

(1)基本思想:
先将整个待排记录序列分割成若干子序列,分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序

(2)优点
缩小增量
多遍插入排序

(3)代码表示

//其中nums是需要排序的数组
//d是希尔分列时的增量,即每个多少间距,选择一个元素
//t是d的长度,即需要排列的次数
void ShellSort(vector<int>& nums, vector<int>& d, int t)
{
	for(int i = 0; i < t; i++)
	{
		Shellsert(nums,d[i]);
	}
}

void Shellsert(vector<int>& nums, int db)
{
	for(int i = db + 1; i < nums.size(); i++)
	{
		int temp = nums[i];
		for(int j = i - db; j >= 0;)
		{
			if(nums[j] < nums[i])
			{
				nums[j + db] = temp;
				break;
			}
			nums[j + db] = nums[j];
			j = j - db;
		}
	}
}

注意:希尔排序是不稳定排序
再排序过程中希尔排序改变了相同大小元素间的相对排序位置,如下面例子中两个8的相对位置发生改变。

在这里插入图片描述
希尔排序的时间复杂度由数组个数和排序次数决定,通常为:
在这里插入图片描述
希尔排序的最后一个增量必须是1,无除1之外的公因子
不易在链式存储结构上实现

二、交换排序

在这里插入图片描述

1.冒泡排序

1、流程:
将每个元素和数组中所有元素进行对比,如果当前下标元素比其后续的元素大,则进行交换。全部遍历后,实现升序排列
2、例子
通过冒泡排序方法,对数组进行升序排列

void paixu(vector<int>& nums)
{
	if(nums.size() == 0)
		return;
	for(int i = 0; i < nums.size(); i++)
	{
		for(int j = i + 1; j < nums.size(); j++)
		{
			if(nums[j] > nums[i])
			{
				swap(nums[i], nums[j]);
			}
		}
	}
}

注意:
1)双层循环,复杂度为n^2

2. 快速排序

1)基本思想
通过一次排序,将待排序的数组分为了两个独立部分,一部分记录的元素比另一部分记录的关键则小,可分别对这两部分记录进行排序,以达到整个序列有序

2)实现流程
(1)选择一个中间数作为参考,所有的元素与之比较,小的调到其左边,大的调到其右边
(2)中间数:中间数是任选的,通常是第一个元素,最后一个元素或者最中间的一个数
(3)多次重复第一步和第二步,只到获得排序数组

3)代码表示

//其中l表示的是nums的左边界
//r表示的是nums的右边界
void quick_sort(vector<int> &nums, int l, int r) {
	if (l + 1 >= r) {
		return;
	}
	//这里面选择了第一个元素作为中间数
	int first = l, last = r - 1, key = nums[first];
	while (first < last){
		while(first < last && nums[last] >= key) {
			--last;
		}
		nums[first] = nums[last];
		while (first < last && nums[first] <= key) {
			++first;
		}
		nums[last] = nums[first];
	}
	nums[first] = key;
	quick_sort(nums, l, first);
	quick_sort(nums, first + 1, r);
}

快速排序不是稳定排序算法

三、选择排序

1.简单选择排序

1)基本思想:在待排序的数组中选择最大(小)的元素放在其最终位置
2)实现流程:
(1)首先通过n-1次关键字比较,从n个记录中找到关键字最小的记录,将其与第一个记录交换
(2)再通过n-2次比较,从剩余的n-1次记录中找到关键字次小的记录,将它与第二个记录交换
(3)重复上卖弄操作,进行n-1次排序后,结束

3)代码表示

void SelectSort(vector<int>& nums)
{
	for(int i = 0; i < nums.size(); i++)
	{
		int min_val = nums[i];
		int k = i;
		for(int j = i + 1; j < nums.size(); j++)
		{
			if(nums[j] < min_val)
			{
				min_val = numa[j];
				k = j;
			}
		}
		swap(nums[i], nums[k]);
	}
}

这种方式,无论排序处于什么状态,选择排序所需进行的比较次数相同
因为每次从后面选择一个最小值,和第i个位置进行交换,
选择排序是不稳定排序

2.堆排序

1)堆定义:
在这里插入图片描述
从堆的定义可以看出,堆是满足二叉树中任一非叶子结点均小于(大于)它孩子结点 的完全二叉树

2)堆排序定义:
若在输出堆顶的最小值(最大值)后,使得剩余n-1个元素的序列重新又建成一个堆,则得到n个元素的次小值(次大值),… 如此反复,便能得到一个有序序列,这个过程叫堆排序

3)堆的调整:
以小根堆为例,介绍堆排序的过程
(1)输出堆顶元素后,以堆中最后一个元素替代
(2)将根节点值与左、右子树的根结点值进行比较,并与其中小者进行交换
(3)重复上述操作,直到叶子结点,将得到新的堆,称这个从堆顶至叶子的调整过程为筛选

4)代码表示:

//s和m表示的是需要排序的数组部分
//以下是建堆代码
void HeapAdjust(vector<int>& nums, int s, int m)
{
	int temp = nums[s];
	for(int i = 2 * s; i <= m; i *= 2)
	{
		//这步是判断左右子树那个元素大,如果右子树大,那么变换到右子树上
		if(i < m && nums[i] < nums[i+1])
			i++;
		if(temp >= nums[i])
		{
			break;
		}
		nums[s] = nums[i];
		s = i;
		
	}
	nums[s] = temp;
}

//以下是堆排序代码
void HeapSort(vector<int>& nums)
{
	int i;
	for(i = nums.size() / 2; i >= 0; i-- )
	{
		HeapAdjust(nums, i, nums.size() - 1);
	}
	//i不等于0是因为下表0对应的元素已经是最小了,不需要排序了
	for(i = 1; i < nums.size(); i++)
	{
		swap(nums[i], nums.back());
		HeapAdjust(nums,i,nums.size() - 1);
	}
}


三、归并排序

将数组分为左右两部分,分别排序再合并

1.代码表示

void merge_sort(vector<int> &nums, int l, int r, vector<int> &temp) {
	if (l + 1 >= r) {
		return;
	}
	// divide
	int m = l + (r - l) / 2;
	merge_sort(nums, l, m, temp);
	merge_sort(nums, m, r, temp);
	// conquer
	int p = l, q = m, i = l;
	while (p < m || q < r) {
		if (q >= r || (p < m && nums[p] <= nums[q])) {
			temp[i++] = nums[p++];
		} else {
			temp[i++] = nums[q++];
		}
	}
	for (i = l; i < r; ++i) {
		nums[i] = temp[i];
	}
}

四、基数排序

基数排序又叫桶排序

1.基本思想:

分配+收集
设置若干个箱子,将关键字为k的记录放入第k个箱子,然后再按序号将非空的链接

2.代码表示

假设原来有一串数值如下所示:
73, 22, 93, 43, 55, 14, 28, 65, 39, 81
首先根据个位数的数值,在走访数值时将它们分配至编号0到9的桶子中:
0
1 81
2 22
3 73 93 43
4 14
5 55 65
6
7
8 28
9 39

第二步:
接下来将这些桶子中的数值重新串接起来,成为以下的数列:
81, 22, 73, 93, 43, 14, 55, 65, 28, 39
接着再进行一次分配,这次是根据十位数来分配:
0
1 14
2 22 28
3 39
4 43
5 55
6 65
7 73
8 81
9 93

第三步:
接下来将这些桶子中的数值重新串接起来,成为以下的数列:
14, 22, 28, 39, 43, 55, 65, 73, 81, 93
这时候整个数列已经排序完毕;如果排序的对象有三位数以上,则持续进行以上的动作直至最高位数为止。

 
/*
* 打印数组
*/
void printArray(int array[],int length)
{
	for (int i = 0; i < length; ++i)
	{
		cout << array[i] << " ";
	}
	cout << endl;
}
/*
*求数据的最大位数,决定排序次数
*/
int maxbit(int data[], int n) 
{
    int d = 1; //保存最大的位数
    int p = 10;
    for(int i = 0; i < n; ++i)
    {
        while(data[i] >= p)
        {
            p *= 10;
            ++d;
        }
    }
    return d;
}
void radixsort(int data[], int n) //基数排序
{
    int d = maxbit(data, n);
    int tmp[n];
    int count[10]; //计数器
    int i, j, k;
    int radix = 1;
    for(i = 1; i <= d; i++) //进行d次排序
    {
        for(j = 0; j < 10; j++)
            count[j] = 0; //每次分配前清空计数器
        for(j = 0; j < n; j++)
        {
            k = (data[j] / radix) % 10; //统计每个桶中的记录数
            count[k]++;
        }
        for(j = 1; j < 10; j++)
            count[j] = count[j - 1] + count[j]; //将tmp中的位置依次分配给每个桶
        for(j = n - 1; j >= 0; j--) //将所有桶中记录依次收集到tmp中
        {
            k = (data[j] / radix) % 10;
            tmp[count[k] - 1] = data[j];
            count[k]--;
        }
        for(j = 0; j < n; j++) //将临时数组的内容复制到data中
            data[j] = tmp[j];
        radix = radix * 10;
    }
}
 
int main()
{
	int array[10] = {73,22,93,43,55,14,28,65,39,81};
	radixsort(array,10);
	printArray(array,10);
	return 0;
}
 
//结果
//14 22 28 39 43 55 65 73 81 93 

五、排序算法对比

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值