内部排序算法1(插入排序)

来源:互联网 发布:网络诈骗按照什么罪 编辑:程序博客网 时间:2024/05/21 09:25

内部排序算法1(插入排序)

重点指标
1. 排序算法的时间代价分析
2. 排序算法的空间代价分析
3. 排序方法的稳定性
4. 静态排序和动态排序
5. 算法的适用性

排序
1. 排序是对数据元素的逻辑顺序或者物理顺序的一种重新排列。
2. 排成非减(递增)顺序称为正序, 排成非增(递减)顺序称为逆序。
3. 排序的依据是排序码(可重复),即元素或记录中的用于作为排序依据的项。
4. 静态排序在排序过程中链表结构不变,可以避免数据移动,但是代价是增加了使用的指针空间。

排序算法的稳定性的判断原则
1. 如果需要把所有元素按排序的升序排列,在每趟从前向后比较选小时把排序码相等的视为更小者,或从后向前比较选大时把排序码相等的视为更大者,使得原本正序的元素视为逆序,一旦交换将导致两个排序码相等的元素发生相对位置的前后颠倒。
2. 在排序的过程中,需要以一个较大的间隔互换数据,或把数据隔空搬运一段较大距离时,排序方法一定不稳定,它可能会把原先排在前面的元素搬到具有相同排序码的另一个元素的后面,颠倒了具有相同排序码的不同元素的相对前后位置。
3. 不稳定的排序算法有4种:Shell排序、简单选择排序、快速排序和堆排序。
4. 稳定排序算法有 种,直接插入排序、折半插入排序、气泡排序、归并排序、锦标赛排序和基数排序。

排序算法的适用于那种数据结构(顺序结构和单链表结构)
1. 适用于顺序表的排序方法有:直接插入排序、折半插入排序、希尔排序、气泡排序、快速排序、简单选择排序、堆排序、归并排序、计数排序和奇偶排序等。
2. 适用于单链表的排序方法有:直接插入排序、气泡排序、简单选择排序、归并排序和基数排序。
3. 适用于树形排序的排序方法有:锦标赛排序(胜者树)、多路归并排序(败者树)、 二叉查找树排序、堆排序等。


排序算法的介绍

1. 插入排序

思路

将待排序的子序列中的一个元素按其排序码的大小插入到已经排好序的有序子序列中的适当位置,使得有序子序列扩大一个元素。如此继续,直到待排序子序列中的所有元素取空并都插入到有序子序列。

思想

把数组a[n]中待排序的n个元素看成一个有序表和一个无序表,开始时有序表中只包含一个元素a[0],无序表中包含有n-1个元素a[1]~a[n-1]。排序过程中每次从无序表中退成第一个元素,把它插入到有序表中的适当位置,使之成为新的有序表,这样经过n-1次退出和插入后,无序表就变成空表了,有序表中就包含了所有的元素。

图示


这里写图片描述
图片来自:几种基本的插入排序

算法描述

//插入排序头文件#pragma once#include<iostream>typedef int DataType;class InsertSort{public:    InsertSort(int length);    void create();    void sort();    void print();private:    DataType *data;    int len;};InsertSort::InsertSort(int length){    len = length;    data = new DataType[len];}inline void InsertSort::create(){    std::cout << "请输入要排序的序列: " << std::endl;    for (int i = 0; i < len; i++)    {        int temp;        std::cin >> temp;        data[i] = temp;    }    std::cout << "输入完毕" << std::endl;}inline void InsertSort::sort(){    int i, j;    for (i = 1; i < len; i++)    {        if (data[i] < data[i - 1])      //当序列放生逆序的时候才插入,否则跳过。        {            int temp = data[i];         //记录需要排序的那个值,然后从后往前比较并移动数据。            for (j = i - 1; temp < data[j] && j >= 0; j--)            {                data[j + 1] = data[j];            }            data[j + 1] = temp;        }    }}inline void InsertSort::print(){    for (int i = 0; i < len; i++)    {        std::cout << data[i] << " ";    }    std::cout << std::endl;}

main文件

//插入排序main文件using namespace std;#include"InsertSort.h"int main() {    InsertSort insertSort(10); //排序的序列中有十个元素    insertSort.create();       //输入十个数    insertSort.sort();         //排序    insertSort.print();        //输出序列    system("pause");}

结果


这里写图片描述

算法分析

事件复杂度分析

每次在有序的子序列中从后往前寻找插入元素应该插入的位置。这种方式导致算法事件代价严重依赖于待排序的序列的初始排列。最好的情况下,当初始排列全部有序(从小到大)的情况下,每趟插入元素只需要和有序序列的最后一个元素作比较,并不需要移动,总的比较次数(KCN)为n-1次, 移动次数(RMN)为0次。 在最坏的情况下,即待排序元素的序列中所有元素已经从大到小排列(反序),若想将它们翻转过来,则第i个元素要和前面i-1个元素做比较,总的比较次数为1+2+…+(n-1) = (n-1)*n/2次。在每一次比较之后都需要移动一次,加上取出该元素和放入指定的位置两次,也就是ni=0(i+2)=(n+4)(n1)/2 次。在平均的情况下,比较次数和移动次数约为n2/4。因此,直接插入排序的时间复杂度为O(n2)

空间复杂度分析

只需要一个存储单元存放暂要插入的元素。即空间复杂度为O(1)

稳定性分析

算法是稳定的,从后往前比较的时候没有用等于号。

2. 折半插入排序

思路

设在数据表中有一个元素序列a[0]~a[n-1], 其中,a[0]~a[i-1]已经排好序。在插入a[i](i = 1, 2, …, n-1)时,利用折半查找法在有序表a[0]~a[i-1]内寻找a[i]的插入位置。

算法

//  折半插入排序头文件//  HalfInsertSort.h//  HalfInsertSort////  Created by zcs on 2017/4/26.//  Copyright © 2017年 ZCS-Company. All rights reserved.//#ifndef HalfInsertSort_h#define HalfInsertSort_htypedef int DataType;#include<iostream>class HalfInsertSort{    DataType *data;    int len;public:    HalfInsertSort(int length);    void create();    void print();    void sort();};HalfInsertSort::HalfInsertSort(int length){    len = length;    data = new DataType[len];}inline void HalfInsertSort::create(){    std::cout << "请输入要排序的序列" << std::endl;    for (int i = 0; i < len; ++i)    {        int temp;        std::cin >> temp;        data[i] = temp;    }    std::cout << "完成输入" << std::endl;}inline void HalfInsertSort::print(){    for (int i = 0; i < len; ++i)    {        std::cout << data[i] << " ";    }    std::cout << std::endl;}inline void HalfInsertSort::sort(){    int i, j, low, high, mid = 0;    DataType temp;    for (i = 1; i < len; ++i) {        if (data[i] < data[i - 1]) { //如果没有发生逆序,则跳过循环。            temp = data[i];            low = 0;            high = i - 1;            while (low <= high) {    //用折半查找法查找插入的位置                mid = low + (high - low) / 2;                if (temp < data[mid])  //如果需要插入的值比前面的值要小,则往前移动。                {                    high = mid - 1;                   }                else  //否则,不管相等还是大于都要往后移动,保证算法的稳定性。                {                    low = mid + 1;                }            }            for (j = i; j > low; --j) {                data[j] = data[j - 1];            }            data[low] = temp;        }        std::cout << "第" << i << "趟: ";        print();    }}#endif /* HalfInsertSort_h */
//  折半插入排序main文件//  main.cpp//  HalfInsertSort////  Created by zcs on 2017/4/26.//  Copyright © 2017年 ZCS-Company. All rights reserved.//#include <iostream>#include "HalfInsertSort.h"int main(int argc, const char * argv[]) {    HalfInsertSort sort(10);    sort.create();    sort.sort();    return 0;}

结果


结果图

算法分析

时间复杂度

排序码的比较次数与待排序元素的初始排列无关,插入第i(i=1, 2, …, n-1)个元素时,排序码比价次数大约是logi+12 , 总的排序码比较次数为n1i=1(logi+12)=logn!2, 约等于nlogn2 次。折半插入的元素移动次数与待排序的元素的初始位置有关。最好的情况下,所有的元素已经按排序码从小到大排好序,元素的移动次数为0次。最坏的情况下, 元素的移动次数与插入排序的情况相同,为(n+4)(n1)/2 次。

空间复杂度

算法的空间复杂度为O(1), 只需要一个存储单元来存储将要插入的元素。

算法的稳定性

折半插入排序算法是稳定的,不会改变相同大小元素的前后顺序。

与直接插入排序比较

就平均性能来说,折半插入排序比直接插入排序要快。当n比较大时,总排序码比较次数比直接插入排序的最差情况要好得多。折半插入排序的元素移动次数与直接插入排序的移动次数相同。

3. 希尔排序

基本思想

每趟按照一个增量app作为间隔,将全部元素序列分为gap个子序列,所有距离为gap的元素放在同一个子序列中,在每一个子序列中分别施行直接插入排序。然后缩小增量gap,重复上面的子序列划分和排序工作。直到最后取gap = 1,将所有的元素放在同一个序列中排序为止。由于开始时gap的取值较大,每个子序列中的元素较少,排序速度较快;待排序的后期,gap取值逐渐变小,子序列中元素个数逐渐变多,但由于前面工作的基础,大多数元素已基本有序,所以排序的速度仍然很快。

图示


这里写图片描述

图片来自:http://www.cnblogs.com/jingmoxukong/p/4303279.html

算法实现

//希尔排序头文件#pragma once#include<iostream>typedef int DataType;class ShellSort{public:    ShellSort(int length);    void create();    void print();    void sort();    ~ShellSort();private:    DataType *data;    int len;    void insertSort(int start, int gap);};ShellSort::ShellSort(int length){    len = length;    data = new DataType[len];}inline void ShellSort::create(){    std::cout << "请输入要排序的序列: " << std::endl;    for (int i = 0; i < len; i++)    {        int temp;        std::cin >> temp;        data[i] = temp;    }    std::cout << "输入完毕" << std::endl;}inline void ShellSort::print(){    for (int i = 0; i < len; i++)    {        std::cout << data[i] << " ";    }    std::cout << std::endl;}inline void ShellSort::sort(){    int start, gap, i =0;    for (gap = len / 2; gap > 0; gap /= 2)  //增量为gap    {        for (start = 0; start < gap; start++)  //对每一小块进行插入排序        {            insertSort(start, gap);        }        ++i;        std::cout << "第 " << i << "次希尔排序: ";        print();    }}ShellSort::~ShellSort(){    delete[] data;}inline void ShellSort::insertSort(int start, int gap) //插入排序,起点为start, step为gap{                                                     //对每一个部分进行插入排序。    DataType temp;    int i, j;    for (i = start + gap; i < len; i = i + gap)    {        if (data[i] < data[i - gap])  //若没有发生逆序,则跳出循环        {            temp = data[i];            for (j = i - gap; j >= 0 && temp < data[j]; j = j - gap)            {                data[j + gap] = data[j];            }            data[j + gap] = temp;        }    }}
//main函数using namespace std;#include"ShellSort.h"int main() {    ShellSort insertSort(10); //排序的序列中有十个元素    insertSort.create();       //输入十个数    insertSort.sort();         //排序    system("pause");}

结果


希尔排序运行结果

算法分析

时间复杂度

当n很大的时候,希尔排序的排序码平均比较次数和元素平均移动次数大约在n1.25 ~ 1.6n1.25之间,这是利用直接插入排序作为子序列排序方法的情况下得到的。对于规模较大的序列(n>1000)希尔排序具有很高的效率。

空间复杂度

算法的空间复杂度与直接插入排序法一致,只需要一个存储空间储存需要插入的元素

算法的稳定性

希尔排序是不稳定的

注:本文参考书籍《数据结构精讲与习题详解—考研辅导与答疑解惑》,殷人昆编著,清华大学出版社。

0 0
原创粉丝点击