数据结构—排序

1.插入排序
主要代码:
for (int i = 1; i < n; i++)
{
int tmp = arr[i];
j = i;
for (; j >= 0 && tmp < arr[j-1]; j–)
{
arr[j] = arr[j-1];
}
arr[j] = tmp;
}

时间复杂度:O(n)~O(n^2)之间。
稳定性:稳定
2.冒泡排序
主要代码:
for (i = 0;i < n; i++)
{
for (j = 1;j < n-i;j++)
{
if (arr[j] < arr[j-1])
{
int tmp = arr[j-1];
arr[j-1] = arr[j];
arr[j] = tmp;
}
}
}

时间复杂度:O(1)~O(n^2)
稳定性:稳定
3.希尔排序
主要代码:
for (gap = n/2; gap > 0; gap /= 2)
{
for (i = gap; i < n; i++)
{
int tmp = arr[i];
for (j = i; tmp < arr[j-gap] && j >= gap; j -= gap)
{
arr[j] = arr[j-gap];
}
arr[j] = tmp;
}
}

时间复杂度:O(nlogn)~O(n^2)之间
稳定性:由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。
4.归并排序
归并排序是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
在这里插入图片描述
可以看到这种结构很像一棵完全二叉树,本文的归并排序我们采用递归去实现(也可采用迭代的方式去实现)。分阶段可以理解为就是递归拆分子序列的过程,递归深度为log2n。
在这里插入图片描述
在这里插入图片描述
图来源:https://www.cnblogs.com/chengxiao/p/6194356.html
主要程序:
void merge(int* a, int start, int mid, int end)
{
int *tmp = new int[end-start+1]; // tmp是汇总2个有序区的临时区域
int i = start; // 第1个有序区的索引
int j = mid + 1; // 第2个有序区的索引
int k = 0; // 临时区域的索引

while(i <= mid && j <= end)
{
if (a[i] <= a[j])
tmp[k++] = a[i++];
else
tmp[k++] = a[j++];
}

while(i <= mid)
tmp[k++] = a[i++];

while(j <= end)
tmp[k++] = a[j++];

// 将排序后的元素,全部都整合到数组a中。
for (i = 0; i < k; i++)
a[start + i] = tmp[i];

delete[] tmp;
}

void mergeSortUp2Down(int* a, int start, int end)
{
if(a==NULL || start >= end)
return ;

int mid = (end + start)/2;
mergeSortUp2Down(a, start, mid); // 递归排序a[start…mid]
mergeSortUp2Down(a, mid+1, end); // 递归排序a[mid+1…end]

// a[start…mid] 和 a[mid…end]是两个有序空间,
// 将它们排序成一个有序空间a[start…end]
merge(a, start, mid, end);
}
时间复杂度:O(NlogN)<O(N^2)
稳定性:不稳定
5.快速排序
主要代码:
void Swap(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}

int median(int arr, int left, int right)
{
int center = (left + right) / 2;
if (arr[center] < arr[left])
{
Swap(&arr[center], &arr[left]);
}
if (arr[right] < arr[left])
{
Swap(&arr[left], &arr[right]);
}
if (arr[right] < arr[center])
{
Swap(&arr[right], &arr[center]);
}
Swap(&arr[center], &arr[right - 1]);
return arr[right - 1];
}
void quickSort(int arr, int left, int right)
{
if (left < right)
{
int pivot = median(arr, left, right);
//printf("%d\n", pivot);
int i = left;
int j = right - 1;
while (i < j)
{
while (arr[++i] < pivot) {}
while (pivot < arr[–j]) {}
if (i < j)
{
Swap(&arr[i], &arr[j]);
}
else
{
break;
}
}
Swap(&arr[i], &arr[right - 1]);
quickSort(arr, left, i - 1);
quickSort(arr, i + 1, right);
}
}
时间复杂度:O(NlogN)<O(N^2)
当待排序的记录数为n且较大时,快速排序是目前为止在平均情况下速度下速度最快的一种排序方法。
稳定性:不稳定。
原因:
举个例子:
待排序数组:int a[] ={1, 2, 2, 3, 4, 5, 6};
在快速排序的随机选择比较子(即pivot)阶段:
若选择a[2](即数组中的第二个2)为比较子,,而把大于等于比较子的数均放置在大数数组中,则a[1](即数组中的第一个2)会到pivot的右边, 那么数组中的两个2非原序(这就是“不稳定”)。
6.堆排序
参考:https://www.cnblogs.com/chengxiao/p/6129630.html;http://www.cnblogs.com/skywang12345/p/3602162.html
堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。首先简单了解下堆结构。
下面,通过图文来解析堆排序的实现过程。注意实现中用到了"数组实现的二叉堆的性质"。
在第一个元素的索引为 0 的情形中:
性质一:索引为i的左孩子的索引是 (2
i+1);
性质二:索引为i的左孩子的索引是 (2
i+2);
性质三:索引为i的父结点的索引是 floor((i-1)/2);
在这里插入图片描述
例如,对于最大堆{110,100,90,40,80,20,60,10,30,50,70}而言:索引为0的左孩子的所有是1;索引为0的右孩子是2;索引为8的父节点是3。

主要代码:
void Swap(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}

void maxStackdown(int arr, int j, int n)
{
int c = j; //当前节点的位置
int l = 2 * c + 1; //当前节点的左孩子
int tmp = arr[c]; //当前节点的大小
for (; l <= n; c=l, l=2
l+1)
{
if (l < n && arr[l] < arr[l+1])
{
l++;
}
if (tmp >= arr[l])
{
break;
}
else
{
arr[c] = arr[l];
arr[l] = tmp;
}
}
}

void stackSort(int *arr, int n)
{
int i = (n+1) / 2 - 1; //确定结点的个数;
//printf("%d\n", i);
int j = i;
for (; j >= 0; j–)
{
maxStackdown(arr, j, n);
}
//从最后一个元素开始对序列进行调整,不断的缩小调整的范围直到第一个元素
for (i = n; i > 0; i–)
{
//交换a[0]和a[i]
int tmp = arr[0];
arr[0] = arr[i];
arr[i] = tmp;
maxStackdown(arr, 0, i-1);
}
}

时间复杂度:
堆排序的时间复杂度是O(NlgN)。
假设被排序的数列中有N个数。遍历一趟的时间复杂度是O(N),需要遍历多少次呢?
堆排序是采用的二叉堆进行排序的,二叉堆就是一棵二叉树,它需要遍历的次数就是二叉树的深度,而根据完全二叉树的定义,它的深度至少是lg(N+1)。最多是多少呢?由于二叉堆是完全二叉树,因此,它的深度最多也不会超过lg(2N)。因此,遍历一趟的时间复杂度是O(N),而遍历次数介于lg(N+1)和lg(2N)之间;因此得出它的时间复杂度是O(N
lgN)。

堆排序稳定性:
堆排序是不稳定的算法,它不满足稳定算法的定义。它在交换数据的时候,是比较父结点和子节点之间的数据,所以,即便是存在两个数值相等的兄弟节点,它们的相对顺序在排序也可能发生变化。
算法稳定性 – 假设在数列中存在a[i]=a[j],若在排序之前,a[i]在a[j]前面;并且排序之后,a[i]仍然在a[j]前面。则这个排序算法是稳定的。

7.桶排序
桶排序(Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶里。每个桶再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序),最后依次把各个桶中的记录列出来记得到有序序列。桶排序是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是比较排序,他不受到O(n log n)下限的影响。
基本思想
桶排序的思想近乎彻底的分治思想。
桶排序假设待排序的一组数均匀独立的分布在一个范围中,并将这一范围划分成几个子范围(桶)。

然后基于某种映射函数f ,将待排序列的关键字 k 映射到第i个桶中 (即桶数组B 的下标i) ,那么该关键字k 就作为 B[i]中的元素 (每个桶B[i]都是一组大小为N/M 的序列 )。

接着将各个桶中的数据有序的合并起来 : 对每个桶B[i] 中的所有元素进行比较排序 (可以使用快排)。然后依次枚举输出 B[0]….B[M] 中的全部内容即是一个有序序列。
补充: 映射函数一般是 f = array[i] / k; k^2 = n; n是所有元素个数
为了使桶排序更加高效,我们需要做到这两点:

1.在额外空间充足的情况下,尽量增大桶的数量
2.使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中
3.同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。

实现逻辑
设置一个定量的数组当作空桶子。
寻访序列,并且把项目一个一个放到对应的桶子去。
对每个不是空的桶子进行排序。
从不是空的桶子里把项目再放回原来的序列中。
4. 复杂度分析

平均时间复杂度:O(n + k)
最佳时间复杂度:O(n + k)
最差时间复杂度:O(n ^ 2)
空间复杂度:O(n * k)
稳定性:稳定
桶排序最好情况下使用线性时间O(n),桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,因为其它部分的时间复杂度都为O(n)。很显然,桶划分的越小,各个桶之间的数据越少,排序所用的时间也会越少。但相应的空间消耗就会增大。
8.基数排序
基数排序(Radix Sort)是桶排序的扩展,它的基本思想是:将整数按位数切割成不同的数字,然后按每个位数分别比较。
具体做法是:将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

基数排序图文说明
通过基数排序对数组{53, 3, 542, 748, 14, 214, 154, 63, 616},它的示意图如下:
在这里插入图片描述
在上图中,首先将所有待比较树脂统一为统一位数长度,接着从最低位开始,依次进行排序。

  1. 按照个位数进行排序。
  2. 按照十位数进行排序。
  3. 按照百位数进行排序。
    排序后,数列就变成了一个有序序列。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值