数据结构笔记--总结各种排序算法及其应用

排序分为直接插入类排序、交换类排序、选择类排序和归并排序。
注:为了满足每个函数只有一个数组首地址、一个数组长度的要求,我们会对某些函数进行封装。
直接插入类排序:在一个已排好序的记录子集的基础上,每一步将下一个待排序的记录有序的插入到已排好序的记录子集中,直到将所有的待排记录全部插入为止。
直接插入排序:通俗点说,就是把第i个元素插入到前i-1个有序集合并使得插入之后的集合有序。具体做法就是将第i个元素依次与前i-1个元素进行比较,并且将前面的元素依次后移,直到找到一个合适的位置(如果是增序则应插入第一个小于或等于待插入元素的后面)。依次遍历所有元素。

// 直接插入排序
template <typename T>
void InsertSort(T *data, int len)
{
    if (len <= 0)
        return;
    for (int i = 1; i < len; ++i)
    {
        int j = i - 1;
        T temp = data[i];
        while (j >= 0 && temp < data[j])
        {
            data[j + 1] = data[j];
            j--;
        }
        data[j + 1] = temp;
    }
}

折半查找排序:与直接插入排序类似,但不同的是,在直接插入排序中,寻找待插入元素(第i个元素)的位置时,使用的顺序查找。然而,已知待插入元素前i-1个元素有序(从插入类排序定义可知),为了提高查找速度,此时可以使用二分查找。

// 折半插入排序
template <typename T>
void BinInsertSort(T *data, int len)
{
    if (len <= 0)
        return;
    for (int i = 1; i < len; ++i)
    {
        int temp = data[i];
        int low = 0, high = i - 1;
        // 与直接插入不同点
        while (low <= high)
        {
            int mid = (low + high) / 2;
            if (temp < data[mid])
                high = mid - 1;
            else
                low = mid + 1;
        }
        // 元素依次后移
        for (int j = i - 1; j >= low; --j)
        {
            data[j + 1] = data[j];
        }
        data[low] = temp;
    }
}

希尔排序:希尔排序又称增量缩小排序法,是一种基于插入思想的排序方法。先将待排序记录分割成若干个(具体个数与增量有关)子序列,然后对这若干个序列进行直接插入排序。其中,增量缩小为1时,希尔排序就是复杂度很小的直接插入排序。

// 一次增量为d的直接排序
template <typename T>
void ShellSortPart(T *data, int len, int d)
{
    for (int i = d; i < len; ++i)
    {
        int j = i - d;
        T temp = data[i];
        while (j >= 0 && temp < data[j])
        {
            data[j + d] = data[j];
            j = j - d;
        }
        data[j + d] = temp;
    }
}
// 希尔排序,封装上述函数
template <typename T>
void ShellSort(T *data, int len)
{
    if (len <= 0)
        return;
    int d[] = { 5, 3, 2, 1 };  // 增量序列
    int len2 = sizeof(data) / sizeof(data[0]);

    for (int i = len2; i > 0; --i)
    {
        ShellSortPart(data, len, d[i]);
    }
}

交换类排序:基于交换的排序法是一类通过交换不符合要求(前后顺序不符合排序要求)的元素对。
冒泡排序:从头到尾扫描记录序列(也可从尾到头扫描),比较相邻的两个记录,如果逆序,则交换两个记录。则一次扫描就可以将最大元素(降序排序则为最小元素)交换到最后一位。接下来第二趟,扫描前(i-1)个元素。直到所有元素有序。

// 冒泡排序
template <typename T>
void BubbleSort(T* data, const int len)
{
    if (len <= 0)
        return;
    for (int i = 0; i < len - 1; i++)
    {
        for (int j = 0; j < len - i - 1; j++)
        {
            if (data[j] > data[j + 1])
            {
                mySwap(data[j], data[j + 1]);
            }
        }
    }
}

冒泡排序初级改进:在冒泡排序过程中,如果有一次比较没有发生交换,则数组已经有序。例如对序列{8,6,9,1,2,3,4},遍历三次数组后,序列变为{1,2,3,4,6,8,9},数组已经有序,则在下一次遍历,数组中没有发生交换,此时可以直接退出排序。

// 冒泡排序初级改进
template <typename T>
void PrimBubbleSort(T *data, const int len)
{
    if (len <= 0)
        return;
    bool flag = true;
    for (int i = 0; i < len - 1 && flag; i++)
    {
        flag = false;
        for (int j = 0; j < len - i - 1; j++)
        {
            if (data[j] > data[j + 1])
            {
                mySwap(data[j], data[j + 1]);
                flag = true;
            }
        }
    }
}

冒泡排序再改进:在上述过程中,即使序列已经有序,也需要再遍历一次,才能退出程序。按照同样的思路,可以记录交换发生的最后位置。例如,如果现在有五个元素没有排好顺序的话,但是在这五个元素比较时,如果有两个发生了交换,而后面的没有发生交换,即后面的已经有序,下次排序就可以排序到记录的最后位置。

// 冒泡排序再改进
template <typename T>
void AdvBubbleSort(T *data, const int len)
{
    if (len <= 0)
        return;
    int lastIndex = 0, tempIndex = len - 1;
    for (int i = 0; i < len - 1; i++)
    {
        lastIndex = tempIndex;
        for (int j = 0; j < lastIndex; j++)
        {
            if (data[j] > data[j + 1])
            {
                mySwap(data[j], data[j + 1]);
                tempIndex = j;
            }
        }
        if (lastIndex == tempIndex)
            break;
    }
}

快速排序:太经典了,不讲算法,直接上程序。

// 快速排序
template <typename T>
void QuickSortPart(T *data, int left, int right)
{
    if (left > right)
        return;
    int low = left, high = right;
    while (low < high)
    {
        while (low < high && data[low] < data[high])
            high--;
        if (low < high)
            mySwap(data[low], data[high]);
        while (low < high && data[low] < data[high])
            low++;
        if (low < high)
            mySwap(data[low], data[high]);
    }
    QuickSortPart(data, left, low - 1);
    QuickSortPart(data, high + 1, right);
}
// 快速排序,封装上述函数
template <typename T>
void QuickSort(T *data, int len)
{
    if (len <= 0)
        return;
    QuickSortPart(data, 0, len - 1);
}

选择类排序:每一趟排序再len - i - 1个记录中选取最小的记录作为有序序列中第i个记录。
简单选择排序:第i趟简单选择排序是指通过n-1次比较,从n-i-1个记录中选出最小的记录(如果是增序的话),并与第i个记录交换。直到所有记录排序完成为止。

// 简单选择排序
template <typename T>
void SelectSort(T *data, int len)
{
    if (len <= 0)
        return;
    for (int i = 0; i < len - 1; ++i)
    {
        int k = i;
        int min = data[i];
        for (int j = i; j < len; ++j)
        {
            if (min > data[j])
            {
                min = data[j];
                k = j;
            }
        }
        if (k != i)
            mySwap(data[i], data[k]);
    }
}

堆排序:把待排序的元素看成一颗完全二叉树,然后调整这些节点使其符合大根堆性质,然后交换堆顶元素与最后一个元素。此时最大元素在数组末尾,再调整其余未排序节点为大根堆,重复执行使得所有节点有序。

// 调整堆,从root节点到index节点调整
template <typename T>
void AdjHeap(T *data, int root, int index)
{
    int j = 2 * root + 1;   //最后一个有孩子节点的节点位置
    T temp = data[root];

    while (j <= index)
    {
        if (j < index)//找出左孩子节点与右孩子节点中较大的一个
        {
            if (data[j] < data[j + 1])
            {
                j++;
            }
        }
        if (temp < data[j])//找出根节点与左右孩子节点中较大的一个
        {
            data[(j - 1) / 2] = data[j];
            j = j * 2 + 1;
        }
        else
        {
            break;
        }
    }
    data[(j - 1) / 2] = temp;
}
// 堆排序
template <typename T>
void HeapSort(T *data, int len)
{
    if (len <= 0)
        return;
    int i = 0, index = len - 1;
    //初始化堆
    for (i = (index - 1) / 2; i >= 0; i--)
    {
        AdjHeap(data, i, index);
    }
    //交换最大值与最后一个元素,并调整堆
    for (i = index; i>0; i--)
    {
        mySwap(data[0], data[i]);

        AdjHeap(data, 0, i - 1);
    }
}

归并类排序:归并就是将两个或两个以上的有序序列合并成一个有序序列。假设初始序列含有n个记录,首先将这n个记录看成n个有序的子序列,即每个子序列含有1个元素,即每个子序列有序,然后让这n个子序列两两归并,再次归并基础上,再进行两两归并…

// 合并有序子序列
template <typename T>
void Merge(T *data, int first, int mid, int last, T *temp)
{
    int i = first, j = mid + 1;
    int m = mid, n = last;
    int k = 0;
    while (i <= m && j <= n)
    {
        if (data[i] < data[j])
            temp[k++] = data[i++];
        else
            temp[k++] = data[j++];
    }
    while (i <= m)
        temp[k++] = data[i++];
    while (j <= n)
        temp[k++] = data[j++];

    for (i = 0; i < k; ++i)
        data[i + first] = temp[i];
}
// 部分归并排序
template <typename T>
void MergeSortPart(T *data, int first, int last, T *temp)
{
    if (first >= last)
        return;

    int mid = (first + last) / 2;
    MergeSortPart(data, first, mid, temp);
    MergeSortPart(data, mid + 1, last, temp);
    Merge(data, first, mid, last, temp);
}
// 归并排序,封装上述函数
template <typename T>
void MergeSort(T *data, int len)
{
    if (len <= 0)
        return;
    T *temp = new T[len]();
    MergeSortPart(data, 0, len - 1, temp);
}

努力提高可读性,努力提高健壮性,努力符合编程规范。但是水平有限,能力不够,如果您能发现程序中的错误并愿意提出来,本人不胜感激。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值