排序算法(插入排序、希尔排序、堆排序、归并排序)

插入排序

插入排序由N-1次排序组成,对于i = 1到N-1趟,插入排序保证从0到i位置上的元素是已排序状态
插入排序在第i次排序的时候,把第i个位置上的元素向左移动到[ 0 , i ]范围内的正确的位置上。

算法实现

/*
插入排序
将整个数组分为前后两部分,i(不包括i)之前的部分视为已排序部分,i及i之后的部分视为未排序部分;
令temp = a[i],将a[i]暂时提取出来;
然后把i之前的数依次向后移,直到某个位置j的数比temp小的时候,或已经全部移动完时,结束后移;
把temp插入当前j的位置。
*/
template<typename T>
void insertionSort(vector<T> & a){

    int j; //指向已排序的部分

    for (int i = 1; i < a.size(); i++){

        T temp = a[i];    //提取未排序的第一个数
        每次须比较a[j - 1]和a[j],所以j-1 >= 0,所以 j >= 1
        for (j = i; j>=1 && temp < a[j - 1]; j--){    //已排序的数依次后移
                                                     //直到全部移动完或找到temp的插入位置时结束
            a[j] = a[j - 1];
        }

        a[j] = temp;    //插入temp
    }
}

希尔排序

希尔排序通过比较相距一定间隔的元素来工作,各趟比较所用的距离gap随算法进行而减小, 直到gap==1 只比较相邻元素的最后一趟排序为止。实际上是通过gap变量把一个数组分为gap个小组,对每个小组进行插入排序。
比较所用的距离可以排成一个序列,叫做增量序列,刚开始令gap = v.size() / 2 ,之后每次排序完成后都令gap = gap / 2,最终构成一个大于零的增量序列。

算法实现

/*
gap是增量,通过gap把数组分为若干组,组内下标间隔为gap;
同时,gap的值也是第一组的第二个数的位置;
i依次扫描第一组的第二个,第二组的第二个,第三组的第二个……第gap组的第二个;第一组的第三个,第二组的第三个……直到第gap组的最后一个;
j依次扫描每一组的已排序部分,每次前移的量为gap。
*/

template<typename T>
void shellSort(vector<T> & a){
    int gap = a.size() / 2;     //增量

    for (; gap > 0; gap /= 2){
        //类似插入排序,每个小组下标间隔为gap
        for (int i = gap; i < a.size(); i++){
            int j;
            T temp = a[i];
            //每次须比较a[j - gap]和a[j],所以j-gap >= 0,所以 j >= gap
            for (j = i; j >= gap && temp < a[j - gap]; j -= gap){
                a[j] = a[j - gap];
            }
            a[j] = temp;
        }
    }
}

堆排序

优先队列可以用于以O(NlogN)的时间进行排序,是一种最佳的排序方法。
不同于优先队列的构造方法,堆排序并不重新建立一个新的数组创建堆,而是直接在原数组的基础上建立一个堆。因为数组是从下标0开始,而二叉堆是从下标1开始,所以在进行下滤操作的时候,数组的下标应在堆操作的基础上加以转换:
(如果对于下滤和上滤有疑惑可以查看《二叉堆中上滤和下滤的问题》

节点堆中节点数组中节点
节点i的下标ii-1
节点i的左孩子节点下标i*2i*2+1 ((i+1)*2-1 )
节点i的左孩子节点下标i*2+1i*2+2
节点i不是叶子节点(节点i是分支节点)(左孩子的下标小于总个数)i*2 < ni*2+1
节点i有两个孩子(左孩子下标不等于总节点数)i*2 != ni*2+1 != n-1
节点i只有一个孩子(节点i只有一个左孩子)(左孩子下标等于总结点数)i*2 == ni*2+1 == n-1

因为在每次deleteMin()之后,堆中元素的个数都缩小了1,因此可以把堆的最后一个单元存放刚刚删除的堆顶。这样对于转化成最大堆的数组,每次都会把当前堆的最大值放到当前的最后一位,最后形成一个递增的序列。所以堆排序有这样一个规则:构建最大堆产生递增序列,构建最小堆产生递减序列。

堆排序算法的实现过程分为以下几步:
1. 将乱序数组建立成一个最大堆或最小堆。
2. 交换堆顶和堆末的元素
3. 堆的大小减1,重整新的堆,和步骤1的堆序一样
4. 重复步骤2,直到堆的大小为1为止

在别人的博客上发现了一个动态图,很形象地描绘了堆排序的过程,在这里分享一下
这里写图片描述
来源https://www.2cto.com/kf/201609/549335.html

算法实现

template<typename T>
void heapsort(vector<T> & a){
    //建立堆
    for (int i = a.size() / 2; i >= 0; i--){
        percDown(a, i, a.size());
        printVector(a);
    }
    //deleteMax
    for (int j = a.size() - 1; j > 0; j--){
        swap(a[0], a[j]); //交换堆顶和堆末
        percDown(a, 0, j);
    }
}
//返回左孩子的下标
inline int leftChild(int i){

    //return (i + 1) * 2 - 1;
    //即
    return i * 2 + 1;
}

/*
下滤
i是下滤的位置
n是堆的大小
*/
template<typename T>
void percDown(vector<T> & a, int i, int n){
    int child;
    T temp = a[i];
    for (; leftChild(i) < n; i = child){
        child = leftChild(i);
        //n-1为当前二叉堆的最大的编号(该二叉堆从0开始)
        //如果左孩子的编号等于n-1,那么父节点没有右孩子
        if (child != n - 1 && a[child] < a[child + 1]){
            child++;
        }
        if (temp < a[child]){
            a[i] = a[child];
        }
        else{
            break;
        }
    }
    a[i] = temp;
}

归并排序

归并排序是一个以O(NlogN)最坏情况下运行的算法,也是一个非常好的排序算法。
归并排序的基本操作是合并两个已经排序的数组。对于一个无序数组,如果N=1,那么相当于有序数组;否则,递归对把数组的前半部分和后半部分进行归并排序,得到前后两部分有序数组,再对整个数组进行合并。
比如:


    无序数组:      24|13|26|1|2|27|38|15        ->     1231315242627
                             ↓                                  ↑
    第一次递归: 2413261 | 2273815     ->    1132426|231527
                             ↓                                  ↑
    第二次递归: 2413 | 261 | 227 | 3815   ->    1324|126|227|153

算法实现

template<typename T>
void mergesort(vector<T> & a){
    vector<T> tmpArray(a.size());    //辅助数组
    MergeSort(a, tmpArray, 0, a.size() - 1);
}

template<typename T>
void MergeSort(vector<T> &a, vector<T> &tmpArray, int left, int right){
    if (left < right){
        int center = (left + right) / 2;
        MergeSort(a, tmpArray, left, center);
        MergeSort(a, tmpArray, center + 1, right);
        merge(a, tmpArray, left, center + 1, right);  //合并
    }
}

//合并数组
//a是主数组
//tmpArrray是辅助数组,用来暂时存储排好序的数列
template<typename T>
void merge(vector<T> & a, vector<T> & tmpArray, int leftPos, int rightPos, int rightEnd){
    int elementsNum = rightEnd - leftPos + 1;
    int leftEnd = rightPos - 1;
    int insertPos = leftPos;
    while (leftPos <= leftEnd && rightPos <= rightEnd){
        if (a[leftPos] < a[rightPos]){
            tmpArray[insertPos] = a[leftPos];
            leftPos++;
            insertPos++;
        }
        else{
            tmpArray[insertPos] = a[rightPos];
            rightPos++;
            insertPos++;
        }
    }
    while (leftPos <= leftEnd){
        tmpArray[insertPos] = a[leftPos];
        leftPos++;
        insertPos++;
    }
    while (rightPos <= rightEnd){
        tmpArray[insertPos] = a[rightPos];
        rightPos++;
        insertPos++;
    }
    for (int i = 0; i < elementsNum; i++){
        a[rightEnd] = tmpArray[rightEnd];
        rightEnd--;
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值