数据结构之内部排序二

上一节我们讲解三种简单的排序算法,虽然这3种排序算法的排序结果都是稳定的,但是他们的时间复杂度都是O(n^2),所以这三种算法不是最佳的排序方法,今天我们讲几个时间复杂度低于这三种的排序算法。

排序动画二

1.希尔排序

基本思想:将待排序列划分为若干组,在每一组内进行插入排序,以使整个序列基本有序,然后再对整个序列进行插入排序。

例如:将 n 个数据元素分成d 个子序列:
{ R[1],R[1+d],R[1+2d],…,R[1+kd] }
{ R[2],R[2+d],R[2+2d],…,R[2+kd] }

{ R[d],R[2d],R[3d],…,R[kd],R[(k+1)d]}

其中,d 称为增量,它的值在排序过程中从大到小逐渐缩小,直至最后一趟排序减为 1。

从上面的描述,我们知道希尔算法也就一种插入排序,只不过对其进行了优化,减少了查找的烫数。

希尔排序算法代码

#include <stdio.h>
// 打印序列元素
void println(int array[], int len)
{
    int i = 0;
    
    for(i=0; i<len; i++)
    {
        printf("%d ", array[i]);
    }
    
    printf("\n");
}
// 交换序列元素
void swap(int array[], int i, int j)
{
    int temp = array[i];
    
    array[i] = array[j];
    
    array[j] = temp;
}
// 希尔排序算法
void ShellSort(int array[], int len) // O(n*n)
{
    int i = 0;
    int j = 0;
    int k = -1;
    int temp = -1;         // 定义临时变量,存放插入值
    int gap = len;         // 定义变量存放增量
    // DO循环,直至增量等于1
    do
    {
    	// 改变增量值
        gap = gap / 3 + 1; 
        // 顺序选定第i个元素用于插入
        for(i=gap; i<len; i+=gap)
        {
            k = i;
            temp = array[k];       // 当前插入值
            // 以增量gap遍历序列,与当前插入值比较,
            // 如果插入的元素小于当前有序序列的元素,
            // 将当前元素后移gap位并且重新标记插入位置
            for(j=i-gap; (j>=0) && (array[j]>temp); j-=gap)
            {
                array[j+gap] = array[j];
                k = j;
            }        
            // 比较完毕后将选定的元素插入
            array[k] = temp;
        }
        
    }while( gap > 1 );
    
}

int main()
{
    int array[] = {21, 25, 49, 25, 16, 8};
    int len = sizeof(array) / sizeof(*array); 
    
    println(array, len);
    
    ShellSort(array, len);
    
    println(array, len);
    
    return 0;
}

先是将一个长的序列按照增量为gap的分成若干子序列,对子序列进行简单插入排序,然后变更gap的值继续执行上面的操作,当gap值为1时,得出的序列就是排好序的有序序列。特别要注意的是虽然增量序列可以有各种取法,但是应确保增量序列中的值没有除1之外的公因子,并且最后一个增量必须等于1。这就是为什么 有gap = gap / 3 + 1;加一就是确保gap至少为1。至于为什么这里是除以3,我也不得而知,因为到目前为止尚未有人求得最好的增量序列。

快速排序

基本思想:

1.任取待排序序列中的某个数据元素(例如:第一个元素)作为基准,按照该元素的关键字大小将整个序列划分为左右两个子序列:
    1.1.左侧子序列中所有元素都小于或等于基准元素;
    1.2.
右侧子序列中所有元素都大于基准元素;
    1.3. 基准元素排在这两个子序列中间。

2.分别对这两个子序列重复施行上述方法,直到所有的对象都排在相应位置上为止。


快速排序看上去有点像是冒泡排序,其实,快速排序就是对冒泡排序的一种该进。

假设待排序的序列为{R[s],R[s+1],...,R[t]},首先任意选取一个元素(通常可选第一个元素R[s])作为枢轴(pivot),然后按下述

原则重新排列其余元素:

1.将所有关键字较它小的元素安置在它的位置之前;

2.将所有关键字较它大的元素安置在它的位置之后;

由此可以该“枢轴”记录最后所落的位置i作分界线,将序列{R[s],R[s+1],...,R[t]}分割成两个子序列{R[s],R[s+1],...,R[i-1]}和

{R[i+1],R[i+2],...,R[t]}。这个过程称作一趟快速排序(一次划分)。

一趟快速排序的具体做法:

1.假设两个指针low和high,他们的初值分别为low和high;

2.设枢轴记录的关键字为pv;

3.首先从high所指位置起向前搜索,找到第一个关键字小于pv的元素并与枢轴记录元素调换;

4.然后从low所指位置起向后搜索,找到第一个关键字大于pv的元素并与枢轴记录元素调换;

5.重复3、4两步,直至low=high。

代码如下:

// 划分函数
int partition(int array[], int low, int high)
{
	  // 保存枢轴元素
    int pv = array[low];
    // 直至low == high退出循环
    while( low < high )
    {
    	  // 从high所指位置起向前搜索小于pv的元素并与枢轴记录元素调换
        while( (low < high) && (array[high] >= pv) )
        {
            high--;
        }
        
        swap(array, low, high);
        // 从low所指位置起向后搜索大于pv的元素并与枢轴记录元素调换
        while( (low < high) && (array[low] <= pv) )
        {
            low++;
        }
        
        swap(array, low, high);
    }
    // 返回枢轴元素位置下标
    return low;
}

整个快速排序的过程可递归进行。若待排序列中只有一个元素,显然已有序,否则进行一趟快速排序后在分别对划分所得的两个子序列进行快速排序。递归形式的快速算法代码如下:

// 递归快速排序算法
void QSort(int array[], int low, int high)
{
    if( low < high )
    {
    	  // 获取枢轴元素位置(下标)
        int pivot = partition(array, low, high);
        // 对划分所得的两个子序列进行快速排序
        QSort(array, low, pivot-1);
        QSort(array, pivot+1, high);
    }
}

其他代码:

#include <stdio.h>
// 打印序列
void println(int array[], int len)
{
    int i = 0;
    
    for(i=0; i<len; i++)
    {
        printf("%d ", array[i]);
    }
    
    printf("\n");
}
// 交换数据
void swap(int array[], int i, int j)
{
    int temp = array[i];
    
    array[i] = array[j];
    
    array[j] = temp;
}

void QuickSort(int array[], int len) // O(n*logn)
{
    QSort(array, 0, len-1);
}

int main()
{
    int array[] = {21, 25, 49, 25, 16, 8};
    int len = sizeof(array) / sizeof(*array); 
    
    println(array, len);
    
    QuickSort(array, len);
    
    println(array, len);
    
    return 0;
}
虽然 希尔排序,快速排序将排序算法的时间复杂度提高到了O(n* log n),但是 希尔排序和快速排序的排序结果是不稳定的,那么有没有复杂度低又稳定的排序算法呢?当然有了,不过要下一节讲解。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1.实验目的 掌握内排序,比较各种排序的优、缺点。 2 需求分析 2.1原理 2.1.1、直接排序 算法描述:经过i-1遍处理后,L[1..i-1]己排好序。第i遍处理仅将L[i]插入L[1..i-1]的适当位置,使得L[1..i]又是排好序的序列。要达到这个目的,我们可以用顺序比较的方法。首先比较L[i]和L[i-1],如果L[i-1]≤ L[i],则L[1..i]已排好序,第i遍处理就结束了;否则交换L[i]与L[i-1]的位置,继续比较L[i-1]和L[i-2],直到找到某一个位置j(1≤j≤i-1),使得L[j] ≤L[j+1]时为止。 2.1.2、冒泡排序 算法描述:核心思想是扫描数据清单,寻找出现乱序的两个相邻的项目。当找到这两个项目后,交换项目的位置然后继续扫描。重复上面的操作直到所有的项目都按顺序排好。 2.1.3、快速排序 算法描述:首先检查数据列表中的数据数,如果小于两个,则直接退出程序。如果有超过两个以上的数据,就选择一个分割点将数据分成两个部分,小于分割点的数据放在一组,其余的放在另一组,然后分别对两组数据排序。通常分割点的数据是随机选取的。这样无论你的数据是否已被排列过,你所分割成的两个字列表的大小是差不多的。而只要两个子列表的大小差不多。 2.1.4、选择排序 算法描述:首先找到数据清单中的最小的数据,然后将这个数据同第一个数据交换位置;接下来找第小的数据,再将其同第个数据交换位置,以此类推。 2.1.5、堆排序 (1) 基本思想:堆排序是一树形选择排序,在排序过程中,将R[1..N]看成是一颗完全叉树的顺序存储结构,利用完全叉树中双亲结点和孩子结点之间的内在关系来选择最小的元素。 (2) 堆的定义: N个元素的序列K1,K2,K3,...,Kn.称为堆,当且仅当该序列满足特性: Ki≤K2i Ki ≤K2i+1(1≤ I≤ [N/2]) 2.1.6、希尔排序 算法描述:在直接插入排序算法中,每次插入一个数,使有序序列只增加1个节点,并且对插入下一个数没有提供任何帮助。如果比较相隔较远距离(称为增量)的数,使得数移动时能跨过多个元素,则进行一次比较就可能消除多个元素交换。 2.2要求 1.本程序对以下六种常用内部排序算法进行实测比较:冒泡排序,插入排序,选择排序,希尔排序,快速排序,堆排序。 2.排序的元素的关键字为整数。用正序,逆序,不同乱序的数据作测试比较。比较的指标为有关键字参加的比较次数和关键字的移动次数。 3.程序以人机对话的形式进行,每次测试完毕显示各种比较指标值 。 2.3任务 设计一个测试程序比较几种内部排序算法的关键字比较次数和移动次数以取得直观感受。 2.4运行环境 (1)WINDOWSXP系统 (2)C++ 编译环境 3.实验方法 本实验主要是内排序,通过比较的次数和移动的次数判断排序的好坏。主要子函数的说明如下。 1.简单选择排序XuanzePaixu(); 2.冒泡排序MaopaoPaixu(); 3. 直接插入排序CharuPaixu(); 4. 快速排序KuaisuPaixu(); 5. 堆排序DuiPaixu(); 6. 希尔排序 XierPaixu(); 以上的排序算法均采用书中所用的算法。程序采用输入的时候仅输入所要的个数,具体的输入数据由程序随机产生个数,并且输出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值