常用排序算法分析与实现

排序分为两类:内排序和外排序。

内排序:指排序过程中,待排序列全部存放在内存中处理,不需涉及数据的内、外存交换。适用于元素序列不太大的小文件。

外排序:指排序过程中,待排序列不能全部存放在内存中处理,内、外存之间需要多次进行数据交换。适用于元素序列太大,不能一次将其全部放入内存的大文件。

 

内排序分为六类:插入排序、交换排序、选择排序、归并排序、分配排序和计数排序。这里主要介绍前四类。

 

一、插入排序

插入排序是指将无序子序列中的一个或几个元素“插入”到有序子序列中。插入排序主要有直接插入排序,折半插入排序,和希尔(shell)排序。

直接插入排序:时间复杂度为O(n^2),特殊情况(原表有序)为O(n)(稳定)

//如果升序排序

void SimpleInsertSort(int* pData, int length)

{

    if(NULL == pData || length <= 0)

        return ;

    int i, j, temp;

    for(i = 1; i < length; i++)

    {//对待排元素序列进行扫描。从第二个元素开始循环到最后一个元素。

        for(j = i - 1; j >= 0; j--)

        {//从有序序列最后一个元素开始向前扫描,将待插入元素放入合适位置。

            if(pData[j+1] > pData[j])

                break;

            temp = pData[j+1];

            pData[j+1] = pData[j];

            pData[j] = temp;

        }

    }

}

2)折半插入排序:由于插入排序的基本操作是在一个有序表中进行查找和插入,这个查找操作可以利用折半查找来实现,由此称之为折半插入排序。折半插入排序只是减少了元素间的比较次数,而元素的移动次数不变,因此时间复杂度为O(n^2)(稳定)

void BinaryInsertSort(int data[], int length)

{

    if(NULL == data || length <= 0)

        return ;

    int i, j;

    for(i = 1; i < length; i++)

    {//对待排元素序列进行扫描。从第二个元素开始循环到最后一个元素。

        int low = 0;

        int high = i - 1;

        int mid = 0;

        int temp = data[i]; //定义一个temp来存data[i]的值,避免移动序列将其覆盖

        while(low <= high)

        {//[low...high]中折半查找出有序插入的位置,位置为high+1

            mid = (low + high) >> 1;

            if(data[mid] > temp)

                high = mid - 1;

            else

                low = mid + 1;

        }

        for(j = i - 1; j > high; j--)

            data[j+1] = data[j];

        data[high+1] = temp;

    }

}

3)希尔(shell)排序:是对直接插入排序的一种改进,又称缩小增量排序。基本思想是先将待排记录序列以一定的增量间隔d分割成多个子序列,对每个子序列分别进行一次直接插入排序,然后逐步减小增量间隔,重复上述的分组和排序,直至所取的增量为1,即所有记录放在同一组中进行直接插入排序为止。时间复杂度为O(n^(1+u)) (0 < u < 1) (不稳定)

//增量初始值不容易选择,代码只是参考

void ShellSort(int* pData, int length)

{

    int d = length;

    while(d > 1)

    {

        int i, temp;

        d = (d + 1)>> 1;

        for(i = 0; i < length - d; ++i)

        {

            if(pData[i] > pData[i+d])

            {

                temp = pData[i];

                pData[i] = pData[i+d];

                pData[i+d] = temp;

            }

        }

    }

}

 

二、交换排序

交换排序的基本思想是两两比较待排序记录的关键字,两个记录的次序相反时即进行交换,直到没有反序的记录为止。交换排序主要有冒泡排序和快速排序。

1)冒泡排序:时间复杂度为O(n^2),特殊情况(原表有序)为O(n)(稳定)

void BubbleSort(int data[], int length)

{

    int i, j, temp;

    for(i=0; i<length-1; ++i)

    { //外层for循环控制趟数,共n-1,大数放在最后面

        for(j=0; j<length-1-i; ++j)

        {//内层for循环控制相邻元素比较次数

            if(data[j]>data[j+1])

            {

                temp = data[j];

                data[j] = data[j+1];

                data[j+1] = temp;

            }

        }

    }

}

2)快速排序:是对冒泡排序的一种改进,又称“分区交换排序”。基本思想是从待排序列中任选一个记录(通常可选第一个记录),以它作为枢轴点,分别把小于和大于枢轴点的记录移到枢轴点两边。然后在两边序列中重复上述操作,直至全部序列有序。

时间复杂度是O(n*log2(n)), 特殊情况(原表有序,退化为冒泡排序)O(n^2),空间复杂度是O(log2(n))(不稳定)

//划分算法

int Partition(int data[], int low, int high, int length)

{

    if(NULL==data || low<0 || high>=length)

    {

        printf("输入无效参数\n");

        exit(-1);

    }

    int temp = data[low];

    while(low < high)

    {

        while(low < high && data[high]>=temp)

            high--;

        if(low < high)

            data[low++] = data[high];

        while(low < high && data[low]<=temp)

            low++;

        if(low < high)

            data[high--] = data[low];

    }

    data[low] = temp;

    return low;

}

void QuickSort(int data[], int low, int high, int length)

{

    int index;

    if(low < high)

    {

        index = Partition(data, low, high, length);

        QuickSort(data, low, index-1, length);

        QuickSort(data, index+1, high, length);

    }

}

 

三、选择排序

选择排序的基本思想是每一次从待排序序列中选出最小(或最大)的一个元素,存放在已排序列的最后位置,直到全部待排序的记录排定。选择排序主要有简单选择排序和堆排序。

1)简单选择排序:时间复杂度为O(n^2)(不稳定)

//大小到大排序

void SimpleSelectSort(int data[], int length)

{

    int i, j, temp;

    for(i = 0; i < length-1; ++i)

    {

        for(j = i+1; j < length; ++j)

        {

            if(data[i] > data[j])

            {

                temp = data[i];

                data[i] = data[j];

                data[j] = temp;

            }

        }

    }

}

2)堆排序:堆实质上是一棵完全二叉树,树中任一非叶子结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。堆分为小根堆和大根堆两种。小根堆要求父结点小于等于其2个子结点;大根堆要求父结点大于等于其2个子结点。

NN>1)个节点的的完全二叉树编号原则是:从上到下,从左自右。最后一个分枝结点(非叶子结点)的编号为 N/2 取整。且对于编号 i1<=i<=N)有:父结点为 i/2 向下取整;若2i>N,则结点i没有左孩子,否则其左孩子为2i;若2i+1>N,则没有右孩子,否则其右孩子为2i+1

注:使用完全二叉树只是为了好描述算法,它只是一种逻辑结构,真正在实现时我们还是使用数组来存储这棵完全二叉树的。

堆排序时间,主要由“建堆”和反复“调整”重建堆这两部分时间构成。时间复杂度为O(n*log2(n)),空间复杂度为O(1)(不稳定)

void Swap(int* pFirstData, int* pSecondData)

{

    int temp = *pFirstData;

    *pFirstData = *pSecondData;

    *pSecondData = temp;

}

void HeapAdjust(int data[], int startIndex, int length)

{

    int i;

    for(i = 2*startIndex+1; i < length; i = 2*startIndex+1)

    {

        if(i<length-1 && data[i]<data[i+1])

            i++; //较大的记录下标

        if(data[startIndex] >= data[i])

            break;  //不用调整了,满足堆的定义

        Swap(&data[startIndex], &data[i]);

        startIndex = i;

    }

}

void HeapSort(int data[], int length)

{

    int i;

    for(i = length/2-1; i >= 0; --i)

    {

//先建堆,从最后一个非叶结点开始调整,完全二叉树的最后一个非叶结点是n/2

HeapAdjust(data, i, length);

    }

    for(i = length-1; i >= 0; --i)

    {

        Swap(&data[0], &data[i]);

        HeapAdjust(data, 0, i);

    }

}

 

四、归并排序

归并排序是采用分治法的一个典型应用。该算法通过归并操作,将已有序的子序列合并,得到一个整体有序的序列。归并排序主要有二路归并排序。

1)二路归并排序:将一个具有N个待排序记录的序列看成N个长度为1的有序序列,然后进行两两归并,得到(N/2取整)个长度为2的有序序列,再进行两两归并。如此重复,直到得到一个长度为N的有序序列为止。时间复杂度是O(n*log2(n)),空间复杂度是O(n)(稳定)

void Merge(int data[], int copy[], int first, int mid, int last)

{

    int indexCopy = first;

    int i = first; // 前半段第一个数字下标

    int j = mid+1; // 后半段第一个数字下标

    while(i <= mid && j <= last)

    {

        if(data[i] < data[j])

            copy[indexCopy++] = data[i++];

        else

            copy[indexCopy++] = data[j++];

    }

    while(i <= mid)

    {

        copy[indexCopy++] = data[i++];

    }

    while(j <= last)

    {

        copy[indexCopy++] = data[j++];

    }

    for(i = first; i <= last; ++i)

        data[i] = copy[i];

}

/*** 使用递归实现 ***/

void BiMergeSort(int data[], int copy[], int first, int last)

{

    if(first < last)

    {

        int mid = (first+last)>>1;

        BiMergeSort(data, copy, first, mid);

        BiMergeSort(data, copy, mid+1, last);

        Merge(data, copy, first, mid, last);

    }

 

外部排序最常用的是归并排序算法,它由两个阶段组成:

1)  生成初始归并段:将外存文件中的信息分段输入内存,使用内部排序算法对其进行排序,生成初始归并段,并将其写回外部文件,直至外部文件的信息全部转换成初始归并段时为止;(把原始数据分成M段,每段都排好序,分别存入M个文件中,称为顺串文件)

2)  归并:从M个顺串文件中读出头条记录,进行M路归并排序,最小的放到输出文件,同时删除对应的顺串文件中的记录。直至整个外部文件归并为单一归并段时为止。

 

例如:假设有一个含10000个记录的磁盘文件,而当前所用的计算机一次只能对      1,000个记录进行内部排序,则首先利用内部排序的方法得到10个初始归并段,然后进行逐趟归并。假设进行二路归并(即两两归并),则第一趟由10个归并段得到5个归并段;第二趟由 5 个归并段得到3个归并段;第三趟由3个归并段得到2个归并段;最后一趟归并得到整个记录的有序序列。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值