栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

数据结构-第八章 排序-笔记

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

数据结构-第八章 排序-笔记

目录

插入排序

直接插入排序

折半插入排序

希尔排序 

交换排序

冒泡排序

 快速排序 

选择排序

简单选择排序


插入排序 直接插入排序

 代码如下

#define maxsize 100
int main()
{
    int a[maxsize] = { 49, 38, 65, 97, 76, 13, 27, 49 };
    int n = 8;
    //第一个元素a[0]可以看成一个已经排序好的子序列,因此这里可以从第二个元素开始
    for (int i = 1; i < n; i++)
    {
        int temp = a[i];
        int j;
        //遇到<=就退出,可以保持排序的稳定性
        for (j = i - 1; j >= 0 && a[j] > temp; j--)
        {
            a[j + 1] = a[j];
        }
        //注意,在上面的循环退出之前,j会多减一次,例如当a[0] > temp时依旧会进入循环,最终j = -1,
        //因此a[j + 1]才是应该插入的位置
        a[j + 1] = temp;    
    }

    for (int i = 0; i < n; i++)
    {
        cout << a[i] << " ";
    }
    return 0;
}

输出为:

效率分析:

     

若采用链表存储,插入排序时虽然元素的移动次数减少了,但是比较次数还是不变,因此平均时间复杂度依旧是(n²) 

折半插入排序
#define maxsize 100
int main()
{
    int a[maxsize] = { 49, 38, 65, 97, 76, 49, 13, 27, 49 };
    int n = 9;
    //第一个元素a[0]可以看成一个已经排序好的子序列,因此这里可以从第二个元素开始
    for (int i = 1; i < n; i++)
    {
        int temp = a[i], j, low = 0, high = i - 1;
        //二分查找,最终将会有a[high] <= temp <= a[low],将temp插入low位置
        while (low <= high)
        {
            int mid = (low + high) / 2;
            if (a[mid] > temp) high = mid - 1;
            //这里需要注意,当a[mid]==temp时,为了保持算法的稳定性应该继续往右半部分查找
            else low = mid + 1;
        }
        for (j = i - 1; j >= low; j--)
        {
            a[j + 1] = a[j];
        }
        //注意,在上面的循环退出之前,j会多减一次,例如当a[low] > temp时依旧会进入循环,最终j = low-1,
        //因此a[j + 1]才是应该插入的位置
        a[j + 1] = temp;
    }

    for (int i = 0; i < n; i++)
    {
        cout << a[i] << " ";
    }
    return 0;
}

输出为:

  

效率分析:

希尔排序 

 对直接插入排序的一种优化算法

代码如下:

#define maxsize 100
int main()
{
    int a[maxsize] = { 49, 38, 65, 97, 76, 13, 27, 49 };
    int n = 8;  
    for (int d = n / 2; d >= 1; d /= 2) //步长变化,逐次减半至1,当d为1时即直接插入排序
    {
        //当步长为d时,最多可以分为d个子表,而a[0] ~ a[d - 1]分别是这d个子表的首元素
        for (int k = 0; k < d; k++) 
        {
            //每个子表的第一个元素a[k]可以看成一个已经排序好的子序列,因此这里可以从第二个元素a[k + d]开始
            for (int i = k + d; i < n; i++)
            {
                int temp = a[i], j;
                //在访问子表中的相邻元素时要注意步长是d
                for (j = i - d; j >= 0 && a[j] > a[j + d]; j -= d)
                {
                    a[j + d] = a[j];
                }
                //在上面的循环退出之前,j会多减一次d,例如当子表的首元素a[k] > temp时依旧会进入循环,最终j = k-d,
                //因此a[j + d]才是应该插入的位置
                a[j + d] = temp;
            }
        }
    }

    for (int i = 0; i < n; i++)
    {
        cout << a[i] << " ";
    }
    return 0;
}

 输出为:

效率分析及特性: 

 

交换排序

冒泡排序

 才发现我以前一直使用的冒泡排序不知道是从哪里学来的写法,以下是吉大教材的写法,这种写法与王道书上的相反,每趟排序过程会有一个较大元素被放到数组的末尾。

#define maxsize 100
int main()
{
    int a[maxsize] = { 49, 38, 65, 97, 76, 13, 27, 49 };
    int n = 8;
    //根据冒泡排序的特性,n - 1趟排序之后,一定能把相对较大的n - 1个元素都排序好并放到数组的后面,
    //因此循环n - 1次即可完成排序
    for (int i = n - 1; i >= 1; i--)
    {
        bool flag = false;
        for (int j = 0; j < n - 1; j++)
        {
            //只有在a[j] > a[j+1]时才交换,可以保证算法的稳定性
            if (a[j] > a[j + 1])
            {
                int temp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = temp;
                flag = true;
            }
        }
        if (!flag) break;
    }

    for (int i = 0; i < n; i++)
    {
        cout << a[i] << " ";
    }
    return 0;
}

输出为:

 效率分析及特性: 

 快速排序 

 代码如下:

//一趟划分
int partition(int a[], int low, int high)
{
    //这里可以视为在逻辑上移走了low位置的元素,low指向的是一个空元素
    int K = a[low]; //用第一个元素作为枢轴,将待排序序列划分为左右两个部分

    //何时移动low指针和high指针是有讲究的
    //我们每次让指向空元素的指针固定不动,然后移动另外一个指针,再将找到的元素放到空元素位置
    //因此让指向空元素的指针不动,可以视为用这个指针来记录有空元素的位置,然后可以将找到的元素放到这里来
    //而第一次进入大循环前,a[low]位置是空的,因此先移动high指针,之后每次大循环同理
    while (low < high)
    {
        //在low和high指针移动的过程中,也要随时判断两者的大小关系
        while (low < high && a[high] >= K)
        {
            high--;    //不断左移直到找到一个比枢轴小的元素
        }
        //这里可以视为在逻辑上移走了high位置的元素,high指向的是一个空元素
        a[low] = a[high];   //把比枢轴小的元素移动到左端的空元素位置

        while (low < high && a[low] <= K)
        {
            low++;     //不断右移直到直到一个比枢轴大的元素
        }
        //这里可以视为在逻辑上移走了low位置的元素,low指向的是一个空元素
        a[high] = a[low];   //把比枢轴大的元素移动到右端的空元素位置
    }

    //当最终low==high时,a[low]就是枢轴元素应该放置的地方
    a[low] = K;   //此时a[low]左边的元素都比K小,a[low]右边的元素都比K大,枢轴元素排序后的最终位置已经确定
    return low;
}

void quicksort(int a[], int low, int high)
{
    if (low < high) //递归跳出的条件
    {
        int index = partition(a, low, high);    //划分
        quicksort(a, low, index - 1);    //划分左子表
        quicksort(a, index + 1, high);   //划分右子表
    }
}

输出为:

 效率分析及特性:

 

选择排序

简单选择排序

代码如下 

#define maxsize 100
int main()
{
    int a[maxsize] = { 49, 38, 65, 97, 76, 13, 27, 49 };
    int n = 8;  
    //每次将后面最小的元素放到前面,所以每趟形成的子序列一定是有序的,只需n - 1次循环
    for (int i = 0; i < n - 1; i++)
    {
        int min_i = i;  //记录最小元素的下标
        for (int j = i + 1; j < n; j++)
        {
            if (a[j] < a[min_i])
            {
                min_i = j;
            }
        }
        int temp = a[min_i];
        a[min_i] = a[i];
        a[i] = temp;
    }

    for (int i = 0; i < n; i++)
    {
        cout << a[i] << " ";
    }
    return 0;
}

输出为 

 

与直接插入排序的区别:直接插入排序的前i个元素一定是有序子序列,但是不一定是最小的i个元素。而简单选择排序的前i个元素一定是有序子序列,并且也是最小的i个元素。

与冒泡排序的区别:

1.冒泡排序是比较相邻位置的两个数,而简单选择排序是按顺序比较,找最大值或者最小值;

2.冒泡排序每一轮比较后,位置不对都需要换位置,简单选择排序每一轮比较都只需要换一次位置;

3.冒泡排序是通过数去找位置,简单选择排序是给定位置去找数;

效率分析及特性:

 

 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/977196.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号