排序算法分为内排序和外排序,在排序过程中,全部记录存放在内存,则称为内排序,如果排序过程中需要使用外存,则称为外排序。今天我们主要介绍下八种内排序,如下图:
一、直接插入排序
直接插入排序的数据分为两部分已排序部分和待排序部分。每次从待排序部分拿出元素在已排序部分找到合适的位置插入元素。前提是数据量小,基本有序。
例如:给定一个序列{25,45,12,63,48}
图中给出四步,无论什么时候序列中都是两部分已排序部分和未排序部分。每次取未排序部分的第一个元素,与已排序部分的元素进比较之后插入到合适的位置,完成排序。
已排序部分加1,未排序部分减1.依次类推,直到整个排序完成。上述例子需要5步完成排序。具体代码如下:
void InserSort(int *arr,int len) //时间复杂度O(n*n),空间复杂度O(1) 稳定 如果是有序的话时间复杂度是O(n)
{
int tmp;
int i,j;
for(i = 1;i<len;i++)
{
tmp = arr[i];
for(j = i-1;j >= 0;j--) //j标记要挪的开始位置
{
if(arr[j] <= tmp)
{
break;
}
else
{
arr[j+1] = arr[j];
}
}
arr[j+1] = tmp; //找没找到都放到当前小的下一个
}
}
二、希尔排序
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入。增量递减排序:5 2 1(为了最后一次进行全部插入排序),适用于数组较长且初态无序。
例如给定一个例子:{9,1,2,5,7,4,8,6,3,5}
代码如下:
void Shell(int* arr,int len,int gap) //O(n^1.3 --n^1.5) O(1) 不稳定 不管len多长 只用三个变量tmp i j
{
int tmp;
int i,j;
for(i = gap;i<len;i++) //每次以gap个间隔比较
{
tmp = arr[i];
for(j = i-gap;j>=0;j-=gap)
{
if(arr[j] <= tmp)
{
break;
}
else
{
arr[j+gap] = arr[j];
}
}
arr[j+gap] = tmp;
}
}
void ShellSort(int *arr,int len)
{
int d[] = {5,2,1};
for(int i = 0;i<sizeof(d)/sizeof(d[0]);i++)
{
Shell(arr,len,d[i]);
}
}
三、选择排序
全部中找到最小的(放第一位),然后再剩下的数里再找最小的(放第二位),依次进行。适用于数据基本有序的序列
给出例子:{12,8,73,58,34}
具体代码如下:
//不稳定 每次挑最小的 5个数字挑4次就出来了 时间复杂度O(n*n)
{
int min;
int tmp = 0;
int j;
for(int i = 0;i < len - 1;++i)
{
min = i;
for(j = i+1;j < len ;++j)
{
if(arr[j] < arr[min])
{
min = j;
}
}
if(min != i) //将其写出来,当只剩一个数的时候,就可以减少一次无意义的替换
{
tmp = arr[i];
arr[i] = arr[min];
arr[min] = tmp;
}
}
}
四、堆排序
堆:本质是一种数组对象。特别重要的一点性质:任意的叶子节点小于(或大于)它所有的父节点。对此,又分为大顶堆和小顶堆,大顶堆要求节点的元素都要大于其孩子,小顶堆要求节点元素都小于其左右孩子,两者对左右孩子的大小关系不做任何要求。 适用于记录数较大的情况。
过程:
第一步:根据所给的记录序列,构建出一棵完全二叉树;
第二步:对该完全二叉树进行调整,将其调整为大堆或者小堆;构造大堆时,需要从最后一个子树开始从后往前多次调整,每次调整的过程都是从上往下。
第三步:将最后一个元素和对顶元素进行交换,得到最大或最小的元素值;该最大或最小的元素值位于堆的最后一个叶子节点处;
第四步:调整除最后一个元素外的堆,使其成为初始定义的大堆或者小堆;
第五步:重复三、四步,直到整个序列有序为止;
以例子{12,5,28,23,19,46,80,44}分析如下:
具体代码如下:
/*堆排序 有序无序不影响 跳跃式交换数据不稳定*/
/*大根堆 父>子 小根堆 子>父*/
/*父-》子 左子树 2i + 1.右子树 2i + 2*/
/*子-》父 (n-1)/2*/
static void Heapjust(int *arr,int start,int end) //时间复杂度logn 空间复杂度 1
{
int tmp = arr[start];
int parent = start;
for(int i = 2*start + 1;i <= end;i = 2*i + 1) //从左孩子i找 每次以下个左孩子的位置为跳度
{
if(i + 1 <=end && arr[i] < arr[i+1] ) //左孩子与右孩子比较大小
{
i++; //保证i保存较大孩子的下标
}
if(arr[i] > tmp)
{
arr[parent] = arr[i]; //大的值赋值给父节点
parent = i; //对于当前的值,如果有子树继续作为根节点调整
}
else //其值小于它的parent,就直接跳出把tmp的值放到parent的位置
{
break; //根节点与子节点比已经是最大的值
}
}
arr[parent] = tmp;
}
void Headsort(int *arr,int len) //最后一个非叶子节点的位置是len/2 - 1 //nlogn
{
//建立大根堆 nlogn
int tmp;
int i;
for(i = len/2 -1;i >= 0;--i)
{
Heapjust(arr,i,len-1); //调整不同子树的结构 设为最大树的最后一个节点的下标 适当放大点 最后形成大根堆
}
for(i = len - 1;i >= 0;--i) //交换节点的过程
{
tmp = arr[0];
arr[0] = arr[i];
arr[i] = tmp;
Heapjust(arr,0,i-1); //减去对应的最后一个点 然后得重新排序 形成大根堆
}
}
下一篇接着介绍后四个排序算法~