您的位置:首页 > 其它

简单理解四种排序算法

2015-09-28 21:34 429 查看
    程序猿编程离不开排序,解决问题离不开算法,那么肯定也离不开排序算法。排序算法分为内部排序和外部排序,内部排序是只在内存中进行排序,外部排序则还需要对外存进行访问。常用的内部排序算法为插入排序、交换排序、选择排序和归并排序,下面就简单介绍一下四个排序算法。

插入排序

    插入排序是依次将每个记录插入到一个 已排好序的有序表中去,从而得到一个新的有序表。插入排序类似于军训时教官给同学们排队,首先大家先站好队,然后看谁较高或较矮,然后把他们直接放到应该在的位置。

直接插入排序

    直接插入排序就是直接找到一个需要排序的元素,把它插入到它应该在的位置。

<span style="font-family:KaiTi_GB2312;font-size:18px;">void StraightInsertSort(List R,int n)
{
int i,j;
for(i=2;i<=n;i++) //n为表长,从第二个记录起进行插入
{
R[0]=R[i]; //第i个记录复制为岗哨
j=i-1;
while(R[0].key<R[j].key) //与岗哨比较,直至键值不大于岗哨键值
{
R[j+1]=R[j]; //将第j个记录赋值给第j+1个记录
j--;
}
R[j+1]=R[0]; //将第i个记录插入到序列中
}
}</span>应用直接插入排序给[45  38  66  90  88  10  25  45]排序:
初始关键字    45  38  66  90  88  10  25  45

第一趟            [38  45]  66  90  88  10  25  45         给38排序

第二趟            [38  45  66]  90  88  10  25  45         给66排序,因为66大于45,所以顺序不变

第三趟            [38  45  66  90]  88  10  25  45         给90排序,因为90大于66,所以顺序不变

第四趟            [38  45  66  88  90]  10  25  45         给88排序

第五趟            [10  38  45  66  88  90]  25  45         给10排序

第六趟            [10  25  38  45  66  88  90]  45         给25排序

第七趟            [10  25  38  45  45  66  88  90]         给45排序  

 

交换排序

    交换排序是比较两个记录键值的大小,如果这两个记录键值的大小出现逆序,则交换这两个记录,将键值较小的记录向序列前部移动,键值较大的记录向序列后部移动。

冒泡排序

    冒泡排序是交换排序的一种,因为交换排序是键值大的向后退,所以看起来像大泡下沉,小泡上升,所以称为冒泡排序。

<span style="font-family:KaiTi_GB2312;font-size:18px;">void BubbleSort(List R,int n)
{
int i,j,temp,endsort;
for(i=1;i<=n-1;i++)
{
endsort=0;
for(j=1;j<=n-i-1;j++)
{
if(R[j].key>R[j+1].key) //如果逆序,则交换
{
temp=R[j];
R[j]=R[j+1];
R[j+1]=temp;
endsort=1;
}
}
if(endsort==0) break;
}
}</span>应用冒泡排序算法给[45  38  66  90  88  10  25  45]排序:
初始关键字    45  38  66  90  88  10  25  45

第一趟            38  45  66  88  10  25  45  90            90作为最大值排到最后

第二趟            38  45  66  10  25  45  88  90            88作为剩余序列中的最大值排到最后 

第三趟            38  45  10  25  45  66  88  90            66作为剩余序列中的最大值排到最后

第四趟            38  10  25  45  45  66  88  90            45作为剩余序列中的最大值排到最后

第五趟            10  25  38  45  45  66  88  90            38作为剩余序列中的最大值排到最后

第六趟            10  25  38  45  45  66  88  90            25作为剩余序列中的最大值排到最后

第七趟            10  25  38  45  45  66  88  90            10作为剩余序列中的最大值排到最后

快速排序

    快速排序是交换排序中的一种,实质上是对冒泡排序的一种改进。在n个记录中取某一个记录的键值为标准,通常取第一个记录键值为基准,通过一趟排序将待排记录分为小于或等于这个键值和大于这个键值的两个独立的部分,然后对这两个独立部分再进行快速排序,进而达到整个序列有序。

<span style="font-family:KaiTi_GB2312;font-size:18px;">void QuickSort(List R,int low,int high)
{
if(low<high)
{
temp=QuickPartition(R,low,high);
QuickSort(R,low,temp-1);
QuickSort(R,temp+1,high);
}
}</span>应用快速排序给[45  38  66  90  88  10  25  45]排序:
初始关键字    45  38  66  90  88  10  25  45

第一趟            [25  38  10]  45  [88  90  66  45]

子序列            [25]  10  [38]  45  [45  66]  88  [90]

最后序列        10  25  38  45  45  66  88  90

选择排序

    选择排序是每一次在n-i+1个记录中选取键值最小的记录作为有序序列的第i个记录。

直接选择排序

    直接选择排序是在第i次选择操作中,通过n-i次键值间比较,从n-i+1个记录中选出键值最小的记录,并和第i(i<=i<=n-1)个记录交换。

<span style="font-family:KaiTi_GB2312;font-size:18px;">void SelectSort(List R,int n)
{
int min,i,j;
for(i=1;i<=n-1;i++) //每次循环选出一个最小键值
{
min=i; //先假设第i个键值最小
for(j=i+1;j<=n;j++)
{
if(R[j].key<R[min].key) min=j; //如果键值小于最小键值,则记录下该键值的下标
}
if(min!=i) swap(R[min],R[i]); //将最小键值记录和第i个记录交换
}
}</span>

应用快速排序给[45  38  66  90  88  10  25  45]排序:

初始关键字    45  38  66  90  88  10  25  45

第一趟            10  [38  66  90  88  45  25  45]      先找出最小键值10,应该放在第一位,所以让第一位的45与之交换

第二趟            10  25  [66  90  88  45  38  45]      找出剩余序列最小键值25,与38交换位置

第三趟            10  20  38  [90  88  45  66  45]      找出剩余序列最小键值38,与66交换位置

第四趟            10  20  38  45  [88  90  66  45]      找出剩余序列最小键值45,与90交换位置

第五趟            10  20  38  45  45  [90  66  88]      找出剩余序列最小键值45,与88交换位置

第六趟            10  20  38  45  45  66  [90  88]      找出剩余序列最小键值66,与90交换位置

第七趟            10  20  38  45  45  66  88  [90]      找出剩余序列最小键值88,与90交换位置

堆排序

    要想了解堆排序,首先得明白什么是堆。堆分两种,最大堆和最小堆。简单来说,将键值转换为对应的完全二叉树,完全二叉树根结点为序列中最小键值,而且任一结点的键值都不大于它的两个孩子结点的键值(如果存在孩子结点),这样的堆是最小堆。相反,完全二叉树的根结点为序列中最大键值,而且任一结点的键值都不小于它的两个孩子结点的键值(如果存在孩子结点),这样的堆是最大堆。

<span style="font-family:KaiTi_GB2312;font-size:18px;">void HeapSort(List R)
{
int i;
for(i=n/2;i>=1;i--) //从第n/2个记录开始进行筛选建堆
{
Shit(R,i,n);
}
for(i=n;i>=2;i--)
{
swap(R[1],R[i]); //将堆顶记录和堆中最后一个记录互换
Shift(R,1,i-1); //调整R[1]使R[1],…,R[i-1]变成堆
}
}</span>应用堆排序给[45  38  66  90  88  10  25  45]排序:
先构造初始堆,即让该序列填充完全二叉树:

                     


以建立最小堆为例,38小于45,于是二者交换位置:

       


最下层的45小于90,所以45和90交换位置:

                      


再看右子树,因为10<25<66,所以10和66交换位置:

                      


下面依次的交换过程为:

                     


                     


而最后得到的就是最终需要构建的最小堆。

归并算法

    归并排序是将两个或两个以上的有序表合并成一个新的有序表,合并的方法是比较各子序列的第一个记录的键值,最小的一个就是排序后序列的第一个记录的键值。

二路归并排序

    二路归并拍徐即是将两个有序表合并成一个有序表的排序方法。

<span style="font-family:KaiTi_GB2312;font-size:18px;">void MergeSort(List a,int n)
{
m=1;
while(i<=n-2*h+1)
{
merge(a,b,i,i+h-1,i+2*h-1); //将序列ai,…,ai+h-1和序列ai+h,…,ai+2*h-1合并成序列bi,…,bi+2*h-1
i+=2*h; //下标i移动2*h
}
if(i+h-1<n) //将h<剩余序列长度<2h
merge(a,b,i,i+h-1,n); //将序列ai,…,ai+h-1和ai+h,…,an合并成bi,…,bn
else for(t=i;t<=n;t++) b[t]=a[t]; //剩余序列长度<h,将ai,…,an复制到bi,…,bn
}</span>
应用快速排序给[45  38  66  90  88  10  25  45]排序:

初始关键字    [45]  [38]  [66]  [90]  [88]  [10]  [25]  [45]

一次归并        [38  45]  [66  90]  [10  88]  [25  45]
二次归并       [38  45  66  90]  [10  25  45  88]

三次归并       [10  25  38  45  45  66  88  90]           

总结

    每种排序方法都有自己适合的地方,有时候用某个算法可能适得其反。就比如快速排序时,如果该序列基本有序,那么快速排序反而会麻烦。判断排序算法的方式是时间复杂度,不同的算法时间复杂度是不一样的,同一种排序算法对排列不同序列的时间复杂度不同,总而言之,适合的最好的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  排序算法