在日常生活中我们遇到了很多排序问题,无论是你你所见到的商品标签还是学校里面的成绩排名,这篇文章就让我们来好好看看直接插入、希尔、直接选择、堆、冒泡、归并排序吧~
一、排序的概念
内部排序:数据元素全部放在内存中的排序
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外村之间移动数据的排序。
常见的排序算法
l 插入排序(直接插入排序、希尔排序)
l 选择排序(选择排序、堆排序)
l 交换排序(冒泡排序、快速排序)
l 归并排序(归并排序)
二、插入排序
1.直接插入
1.1基本思想(类似于玩扑克)
把要进行排序的记录按照关键码值的大小,挨个儿插入到已经排好序的有序序列当中,直到把所有的记录插完了为止。
1.2实现流程
当插入第i个元素的时候,前面的数据已经排好序,此时用i位置上面的数字与前面i-1个数字作比较,再插入到合适位置。合适位置之后的元素都后移一位。
1.3特性总结:
A. 元素集合越接近有序,直接插入排序算法的时间效率越高
B. 时间复杂度:O(N^2)
C. 空间复杂度:O(1)
D. 稳定
1.4代码实现
a[0]为有序状态,后面的为无序状态,依次将后面的元素往有序的序列里面进行插入操作。
如果遇到需要插入的情况时:因为涉及到插入到一个位置后,该位置的元素要回退的问题,所以我们采用了三个结点轮回交替的方式,完成了数据的交换
当遇到不需要插入的情况时:则照旧往下移动就行
void InsertSort(int* a,int n)
{
for(int i =0;i<n-1;i++)
{//将[end+1]的值插入到有序区间[0,end]中
int end = i;
int tmp = a[end+1];//初始化tmp的值
while(end > 0)//设a[0]为有序状态
{
if(a[end] > tmp)
{
a[end+1] = a[end];
--end;//把要插入元素的位置空出来
}
else
break;
}
a[end+1] = tmp;
}
}
2.希尔排序
2.1基本思想(缩小增量法)
先选定一个一个整数n,n为分组的元素个数,然后将待排序的所有元素,从头开始分成含有n个元素的个组,在个组里面元素进行排序。然后,取、重复上述分组和排序的工作。当n=1时,所有记录在同一组内排好序。
2.2实现流程
2.3特性总结:
A. 希尔排序是对直接插入排序的优化
B. 当n>1时都是预排序,目的是让数组更接近于有序。当grp ==1时,数组已经接近有序了,这样就会很快。这样整体而言,可以达到优化的效果
C. 时间复杂度:O(N1.3—N2)
D. 不稳定
2.4代码实现
void ShellSort(int* a,int n)
{
int gap = n;
gap = gap/3 + 1;
for(int i = 0;i<n-gap;i++)
{
int end = i;
int tmp = a[end+gap];
while(end >= 0)
{
if(a[end] > tmp)
{
a[end+gap] = a[end];
end -= gap;
}
else
break;
}
a[end + gap] = tmp;
}
}
三、选择排序
每一次从待排序的数据元素中选出最大(或最小)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
1、直接选择排序
1.1基本思想
l 在元素集合中选择关键码最大(或最小)的数据元素
l 如果他不在最后(或第一个)元素,就将该元素与最后(或第一个)元素交换此位置
l 在剩余的集合中,重复上述操作,直到集合剩余1个元素
1.2实现过程
1.3特性
l 算法简单,但是效率不高,实际中很少使用
l 时间复杂度:O(N^2)
l 空间复杂度:O(1)
l 不稳定
1.4代码实现
void Swap(int* p,int* q)
{
int temp = *p;
*p = *q;
*q = temp;
}
void SelectSort(int* a,int n)
{
int begin = 0;
int end = n -1;
while(begin < end)
{
int min = begin;
int max = end;
for(int i = begin ;i<end;i++)
{
if(a[i] < a[min])
min = i;
if(a[i] > a[max])
max = i;
}
Swap(&a[begin],&a[min]);
if(begin == max)
max = min;
Swap(&a[end],&a[max]);
begin ++;
end --;
}
2.堆排序
2.1基本思想
通过堆来进行数据的选择,排升序要建大顶堆(从上往下调整),降序建小顶堆
2.2实现过程
2.3特性
A. 堆排序使用堆来选数,效率高了很多
B. 时间复杂度:O(N*logN)
C.空间复杂度:O(1)
D. 不稳定
2.4代码实现
向下调整的过程,只是双亲结点和孩子结点的比较交换过程。
而整个选择排序的过程是一个不断更新end值而循环的一个向下调整过程
void AdjustDown(int* a, int n, int parent)
{
//建大堆
int child = parent * 2 + 1;//利用数组下标计算左孩子的位置
while (child < n)//当左孩子存在时
{
//判断右孩子是否存在且左右孩子哪个更大
if (a[child] < a[child + 1] && child + 1 < n)
++child;
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
break;
}
}
void HeapSort(int* a, int n)
{
//1、建大堆
for (int i = (n - 2) / 2; i >= 0; --i)
{
AdjustDown(a, n, i);
}
//2、选数
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
--end;
}
}
四、交换排序
根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置。将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动
1.冒泡排序
1.1基本思想
两两比较相邻两个数的值,如果反序则交换,直到没有反序为止
1.2实现过程
1.3特性
A. 容易理解
B. 时间复杂度:O(N^2)
C. 空间复杂度:O(1)
D. 稳定
1.4代码实现
void Swap(int* p,int* q)
{
int temp = *p;
*p = *q;
*q = temp;
}
void BubbleSort(int* a,int n)
{
int end = n-1;
//控制趟数
while(end > 0)
{
//控制交换次数
for(int i = 0;i<end;i++)
{
if(a[i+1] < a[i])
Swap(&a[i].&a[i+1]);
}
end --;
}
}
五、归并排序
1.基本思想
采用分治法的思想。现将一个序列分成若干个子序列,将子序列完全有序,再将已有序的子序列合并。先“分解”再“合并”
2.实现流程
3.特性
A. 因为他的空间复杂度是O(n)对于他的思考更多的是解决在磁盘中的外排序问题
B. 时间复杂度:O(N*logN)
C. 稳定
4.代码实现
void _MergeSort(int* a,int begin,int end,int* tmp)
{
if(begin >= end)
return;
//这样求中间值的方式不会存在越界的情况
int mid = begin + ((end-begin)>>1);
//划分子问题:使区间[begin,mid],[mid+1,end]的数有序,递归划分子区间直至其有序
_MergeSort(a,begin,mid,tmp);
_MergeSort(a,mid+1,end,tmp);
//在区间内实现排序
int begin1 = begin;
int end1 = mid;
int begin2 = mid+1;
int end2 = end;
int index = begin;
//合并的过程
while(begin1 <= end1 && begin2 <= end2)
{
if(a[begin1] < a[begin2])
{
tmp[index++] = a[begin1++];
}
else
{
tmp[index++] = a[begin2];
}
}
while(begin1 <= end1)
tmp[index++] = a[begin1++];
while(begin2 <=end2)
tmp[index++] = tmp[begin++];
//将归并结果拷贝回原数组
index = begin;
while(begin < end)
a[index++] = tmp[begin++];
}
void MergeSort(int* a,int n)
{
int* tmp = (int*)malloc(sizeof(int)*n);
_MergeSort(a,0,n-1,tmp);
free(tmp);
}