大三党,大数据专业,正在为面试准备,欢迎学习交流
。
文章详细总结了插入排序、希尔排序、选择排序、归并排序、交换排序(冒泡排序、快速排序)、基数排序、外部排序。从思想
到代码实现
。往期文章
绪论-数据结构的基本概念
绪论-算法
线性表-顺序表和链式表概念及其代码实现
查找-顺序+折半+索引+哈希
将一组数据元素序列重新排列,使得数据元素序列按某个数据项(关键字)有序。
对于任意的数据元素序列,若排序前后所有相同关键字的相对位置都不变,则称该排序方法称为稳定的排序方法。
若存在一组数据序列,在排序前后,相同关键字的相对位置
发生了变化,则称该排序方法称为不稳定的排序方法。
若整个排序过程不需要访问外存便能完成,则称此类排序问题为内部排序;
反之,若参加排序的记录数量很大,整个序列的排序过程不可能在内存中 完成,则称此类排序问题为外部排序。
定位->挤空->插入
排序过程:整个排序过程为n-1趟插入,即先将序列中第
1个记录看成是一个有序子序列,然后从第2个记录开始,
逐个进行插入,直至整个序列有序
补充 重要 岗哨的目的也是为了防止覆盖 可以思考 如果先移动有序序列就会把无序序列的第一个值覆盖
typedef struct
{ int key;//关键字
float info;//该成员的其他信息
}JD;
void straisort(JD r[],int n)//对长度为n的序列
{
int i,j;
for(i=2;i<=n;i++)
{
r[0]=r[i]//第一个需要排序的设为岗哨 不取因为1看做有序 其余n-1看做无序序列
j=i-1;//从后往前
//需要注意第二个循环如果岗哨比所有的值都要小最后会r[0] 和r[0]自己比较一样能够跳出循环
while(r[0].key<r[j].key)
{
r[j+1]=r[j]
j--;
}
r[j+1]=r[0]
}
}
最好情况分析:如果n个值比较。开始1个为有序 n-1为无序。需要进行n-1趟的排序。当序列为初始就是顺序排列。那么每次取出放入岗哨的值都比有序的大,那么每趟只需要比较一次。比较次数为n-1。由于需要放入岗哨再移动到有序的最后所以每趟移动2次。
最坏情况分析:当序列为初始就是逆序排列。一样是n-1趟排序,第1趟比较次数为2(特别注意 岗哨除了跟第一个值比较 还要跟自己比较一次才能跳出循环 回头看代码 )所以应该是 2,3,4…n 等差数列相加。第1趟移动为3次(1是移动到岗哨 2是有序值后移 3 是岗哨移到有序值之前的位置)所以应该是 3,3,4…n+1 等差数列相加
简单插入排序的本质比较和交换
简单插入排序复杂度由逆序个数决定
如何改进简单插入排序复杂度?
分割成若干个较小的子文件,对各个子文件分别进行直接插入排序,当文件达到基本有序时,再对整个文件进行一次直接插入排序。
对待排记录序列先作“宏观”调整,再作“微观”调整
如下
其中,d 称为增量,它的值在排序过程中从大到小逐渐缩小,直至最后一趟排序减
为 1。
注意看注释,特别注意d[ ]是什么
1三个循环嵌套
2最外循环控制选择不同的d
3二层循环控制依次取出间隔为d的两个值,比较后后移一位
4最内层循环控制 2个值之间的比较换位置
//r[]表示待排序的序列 n表示个数
//d[]表示增量序列 就是d的取值 7 6 5 ..这样
// T表示几种增量序列 d[]的元素个数
void shellsort(JD r[],int
n,int d[],int T)
{
int i,j,k;
JD x;
k=0;
//循环每一趟进行分组,组内进行简单插入排序
}
while(k<T)
{
for(i=d[k]+1;i<=n;i++)
//i为未排序记录的位置
{
x=r[i];
//每组两个数 位置为 i j j为本组i前面的记录位置
j=i-d[k];
while((j>0)&&(x.key<r[j].key))
//组内简单插入排序
{
r[j+d[k]]=r[j];
j=j-d[k];
}
r[j+d[k]]=x;
}
k++;
}
【定理】使用希尔增量的最坏时间复杂度为 o( N2 ).
如下例子
希尔排序算法本身很简单,但复杂度分析很复杂. 他适合于中等数据量大小的排序(成千上万的数据量).
从无序子序列中“选择”关键字最小或最大的记录,并将它加入到有序子序列中,以此方法增加记录的有序子序列的长度。
两层循环
外层循环控制每次比较的序列从n 到n-1 …2 每次减去1(也可以理解成初始坐标从 1 到n-1 逐渐后移)
内层循环找到最小值的坐标
然后交换每趟第一个值和最小值的位置(只变换两个)
void smp_selesort(JD r[], int n){
int i, j, k;
JD x;
for (i = 1;i<n;i++){
k = i;//初始把每趟第一个坐标设为最小关键字坐标
//循环找到未排序的最小关键字下标
for (j = i + 1;j <= n;j++)
{
if (r[j].key<r[k].key)
k = j;
}
//如果最小坐标不是原来的 交换
if (i != k)
{
x = r[i];
r[i] = r[k];
r[k] = x;
}
}
归并——将两个或两个以上的有序表组合成一个新的有序表,叫归并排序
设初始序列含有n个记录,则可看成n个有序的子序列,每个子序列长度为1两两合并,得到【n/2】个长度为2或1的有序子序列再两两合并,……如此重复,直至得到一个长度为n的有序序列为止
待补充
通过“交换”无序序列中的记录从而得到其中关键字
最小或最大的记录,并将它加入到有序子序列中,以此方法增加记录的有序子序列的长度。
两层循环
外层循环控制排序的序列长度 从前n个 到 前n-1个 …前2个
内层循环控制前后两个数两两比较及交换位置。下标1 和 2 ;2 和 3;…
注意这边设置flat标志位 如果一层外层循环中没有交换 那么直接退出外层循环
void bubble_sort(JD r[], int n)
{
int m, i, j, flag = 1;
JD x;
m = n ;
while ((m>1) && (flag == 1))/*趟数*/
{
flag = 0;/*本趟是否有交换操作标识初始化*/
for (j = 1;j < m;j++)//*本趟将最大元素放到为排序序列的最后*/
if (r[j].key>r[j + 1].key)
{
flag = 1;
x = r[j];
r[j] = r[j + 1];
r[j + 1] = x;
}
m--;
}
}
最坏情况一样用等差序列的计算 n-1趟 每趟的值逐渐减小1
交换用到一个额外的空间 所以空间复杂度为1
选择一个枢纽,把其他的元素分为两个不相交的两个集合A1 A2。A1中的元素全部都小于枢纽,A2中的元素全部都大于枢纽。 同样的对A1,A2也这样处理,如此反复。
嵌套的两个while比较难理解,嵌套中其实并没有交换,只是把值覆盖枢纽位置的值,枢纽并没有更换过来。当外循环结束再把枢纽填入到中间位置。此时i=j。
例如初始值 如下 枢纽为10 亮的位置是枢纽的现在位置 代码中实际变化如下
10(i) 8 11 7(j) --》 10比7大 7覆盖10的位置(枢纽的位置) 并没有马上更换原本7为10(也就是没有马上更换枢纽的现在位置)
7 8(i) 11 7(j) --》i后移一个到8比10小 不变
7 8 11(i) 7(j) --》i后移一个到11比10大 11覆盖7的位置(枢纽的位置)
7 8 11(i j) 11 --》i=j结束
7 8 10 11 --》 退出外while循环 把枢纽填入
void qksort(JD r[], int t, int w)
{//t=low,w=high
int i, j, k;//i j表示两个移动的坐标(指针)(头和尾)
JD x;
if (t >= w) return;//结束条件
i = t; j = w; x = r[i]; //赋值操作 同时把第一个值作为枢纽x
while (i<j)
{
while ((i<j) && (r[j].key >= x.key)) j--;//枢轴后面的值大于枢轴
if (i<j) { r[i] = r[j]; i++; }//当不满足时,与枢轴交换
while ((i<j) && (r[i].key <= x.key)) i++;//枢轴前面的值小于枢轴
if (i<j) { r[j] = r[i]; j--; }//不满足,与枢轴交换
}
r[i] = x;
qksort(r, t, j - 1);
qksort(r, j + 1, w);
}
待补充这部分(空间的不是很明白)
先对K0进行排序,并按 K0 的不同值将记录序列分成若干子序列之后,分别对 K1 进行排序,……, 依次类推,直至对最次位关键字Kd-1排序完成为止。
首先按关键字 Kd-1进行排序,然后按关键字Kd-2进行排序,……,依次类推,直最后对最主位关键字K0排序完成为止。
例如三位整数,百位优先级>十位优先级>个位优先级。取值0~9。我们可以给出10个桶如下,每个桶都有头指针节点和尾指针节点,当我们采用低位优先可以将数据根据个位数放入到对应的桶子中,相同桶子数据依次穿成链表。当数据全部放入我们从第一个桶子开始,每一个尾指针指向下一个非空桶的头指针节点。最后拉直,完成。
由于有三个关键字需要进行3趟,具体实现如下图。
类似的原理,我们可以设置头指针和尾指针,但是需要注意的是 f[] e[] 分别记录的是第一个数据的坐标和最后一个数据的坐标。而不是指针。
大多数内排序算法都是利用了内存是直接访问的事实,读写一个数据是常量的时间。如果输入是在磁带上,磁带上的元素只能顺序访问。甚至数据是在磁盘上,效率还是下降,因为转动磁盘和移动磁头会产生延迟。
概述:根据内存的大小将一个有n个记录的文件分批读入内存,用各种内排序算法排序,形成一个个有序片段。
置换选择
置换选择流程(看下图很容易理解)
文字描述可能比较复杂配合看图的实例演示比较容易
假设我们有四条磁带A1,A2,B1和B2,两个用于输入,两个用于输出。开始时数据在A1上
内存一次能排序M个记录
工作流程
1 从输入磁带上一次读入M个记录,对它们进行内排序,然后把已排序片段轮流写到B1和B2。回绕所有的磁带 。–预处理
2 取每条磁带上的第一个已排序片段,把它们归并起来,并把结果写到A1。然后,从每条磁带上取下一个已排序片段,把它们归并起来,结果写到A2。继续这个过程,轮流把结果写到A1和A2,
3 回绕四条磁带,重复同样的步骤,这次使用A磁带作为输入,而B磁带作为输出。
4 重复步骤二和三,直到剩下一个长度为N的已排序片断
实例演示如下
每次读入3个到内存,排序后全部输出到B1。第二次再读入3个到内存排序输出到B2,第三次输出到B1如此反复。
每次B1三个值和B2三个值归并输入到A1,下一次输入到A2如此反复和上面一样。
同理,A1 A2作为输出,每次各取6个归并输入到B1,下一次输入到B2,如此反复
如果还有额外的磁带,则可以用多路归并(multiwaymerge)或K路归并(K-way merge)来减少排序输入数据所需要的归并处理次数。 与两路归并原理一致
时间效益
内容较多,如果有遗漏和错误的地方,欢迎指出。