第八章学习心得

排序

内部排序

插入排序

一、直接插入排序

(一)方法

1.设待排序的记录存放在数组r[1...n]中,r[1]是一个有序序列

2.循环n-1次,每次使用顺序查找法,查找r[i](i = 2,...,n)在已排好序的序列r[1...i-1]中的插入位置,然后将r[i]插入表长为i-1的有序序列r[1...i-1],直到将r[n]插入表长为n-1的有序序列r[1...n-1],最后得到一个表长为n的有序序列

(二)代码

void InsertSort(SqList &L)
{
    for(i = 2;i <= L.length;++i)
        if(L.r[i].key < L.r[i-1].key)//”<"需将r[i]插入有序子表
        {    
            L.r[0] = L.r[i];    //将插入的记录暂存到监视哨中
            L.r[0] = L.r[i-1];    //r[i-1] 后移
            for(j = i - 2;L.r[0].key < L.r[0].key;--j)  //从后往前寻找插入位置  
                L.r[j+1] = L.r[j];    //记录逐个后移,直到找到插入位置
            L.r[j+1] = L.r[0];    //将r[0]即原r[i],插入到正确位置
        }
}
View Code

(三)时间复杂度为O(n2)

(四)空间复杂度为O(1)

(五)算法特点

1.稳定排序

2.算法简便,且容易实现

3.也适用于链式结构,只是在单链表上无需移动记录,只需修改相应的指针

4.更适合于初始记录基本有序(正序)的情况,当初始记录无序,n较大时,次算法时间复杂度较高,不宜采用

二、折半插入排序

(一)方法

1.设待排序的记录存放在数组r[1...n]中,r[1]是一个有序序列

2.循环n-1次,每次使用折半查找法,查找r[i](i = 2,...,n)在已排好序的序列r[1...i-1]中的插入位置,然后将r[i]插入表长为i-1的有序序列r[1...i-1],直到将r[n]插入表长为n-1的有序序列r[1...n-1],最后得到一个表长为n的有序序列

(二)代码

void BInsertSort(SqList &L)
{
    for(i = 2;i <= L.length;++i)
    {
        L.r[0] = L.r[i];    //将待插入的记录暂存到监视哨中
        low = 1;high = i- 1;    //置查找区间初值
        while(low <= high)
        {
            m = (low + high)/2;    //折半
            if(L.r[0].key < L.r[m].key)    high = m-1;    //插入点在前一子表
            else low = m + 1;    //插入点在后一子表
        }
    for(j = i-1;j >= high+1;--j)
        L.r[j+1] = L.r[j];    //记录后移
    L.r[high+1] = L.r[0];    //将r[0]即原r[i],插入到正确位置
    }
}
View Code

(三)时间复杂度为O(n2)

(四)空间复杂度为O(1)

(五)算法特点

1.稳定排序

2.只能用于顺序结构,不能用于链式结构

3.适合初始记录无序,n较大时的情况

三、希尔排序

(一)方法

1.第一趟取增量d1(d1<n)把全部记录分成d1个组,所有间隔为d1的记录分在同一组,在各个组中进行直接插入排序

2.第二趟取增量d2(d2<d1),重复上述的分组和排序

3.依次类推,直到所取的增量dt=1(dt<dt-1<...<d2<d1),所有记录在同一组中进行直接插入排序为止

(二)代码

void ShellInsert(SqList &L,int dk)
{
    for(i = dk+1;i <= L.length;++i)
    {
        if(L.r[i].key < L.r[i-dk].key)    //需将L.r[i]插入有序增量子表
        {
            L.r[0] = L.r[i];    //暂存在L.r[0]
            for(j = i-dk;j>0&&L.r[0].key < L.r[j].key;j-=dk)
                L.r[j+dk] = L.r[j];    //记录后移,直到找到插入位置
            L.r[j+dk] = L.r[0];    //将r[0]即原r[i],插入到正确位置
        }
}

void ShellSort(SqList &L,int dt[],int t)
{
    for(k = 0;k < t;++k)
        ShellInsert(L,dt[k]);    //一趟增量为dt[t]的希尔插入排序
}
View Code

(三)时间复杂度为n(log2n)2

(四)空间复杂度为O(1)

(五)算法特点

1.不稳定的

2.只能用于顺序结构

3.增量序列可以有各种取法,但应该使增量序列中的值没有除1之外的公因子,并且最后一个增量值必须等于1

4.记录总的比较次数和移动次数都比直接插入排序要少,n越大时,效果越明显。所以适合初始记录无序、n较大时的情况

 

交换排序

一、冒泡排序

(一)方法

1.设待排序的记录存放在数组r[1...n]中。首先将第一个记录的关键字和第二个记录的关键字进行比较,若为逆序(即L.r[1].key>L.r[2].key),则交换两个记录。然后比较第二个记录和第三个记录的关键字。以此类推,直至n-1个记录和第n个记录进行过比较为止。上述过程称作第一趟起泡排序,其结果使得关键字最大的记录被安置到最后一个记录的位置上

2.然后进行第二趟起泡排序,对前n-1个记录进行同样操作,其结果是使关键字次大的记录被安置到第n-1个记录的位置上

3.重复上述比较和交换过程,第i趟是从L.r[1]到L.r[n-i+1]依次比较相邻两个记录的关键字,并在“逆序”时交换相邻记录,其结果是这n-i+1个记录中关键字最大的记录被交换到第n-i+1的位置上。直到在某一趟排序过程中没有进行过交换记录的操作,说明序列已全部达到排序要求,则完成排序。

(二)代码

void BubbleSort(SqList &L)
{
    m = L.length - 1;
    flag = 1;    //flag用来标记某一趟排序是否发生交换
    while((m>0)&&(flag == 1))
    {    
        flag = 0;    //flag置为0,如果本趟排序没有发生交换,则不会执行下一趟排序
        for(j = 1;j <= m;j++)
            if(L.r[j].key > L.r[j+1].key)
            {
                flag = 1;    //flag置为1,表示本趟排序发生了交换
                t = L.r[j];L.r[j] = L.r[j+1];L.r[j+1] = t;    //交换前后两个记录
            }
        --m;
    }
}
View Code

(三)时间复杂度为O(n2)

(四)空间复杂度为O(1)

(五)算法特点

1.稳定排序

2.可用于链式存储结构

3.移动记录次数较多,算法平均时间性能比直接插入排序差。当初始记录无序,n较大时,此算法不宜采用

二、快速排序

(一)方法

1.选择待排序表中的第一个记录作为枢轴,将数周记录暂存r[0]的位置上。附设两个指针low和high,初始时分别指向表的上街和下界(第一趟时,low = 1,high = L。length)

2.从表的最右侧位置依次向左搜索,找到第一个关键小于数周关键字pivotkey的记录,将其移到low处。具体操作:当low<high时,若high所指向的关键字大于等于pivotkey,则向左移动指针high(执行操作--high);否则将high所指记录移到low所指记录

3.然后再从表的最左侧位置,依次向右搜索找到第一个关键字大于pivotkey的记录和枢轴记录交换。具体操作时:当low<high时,若low所指记录的关键字小于等于pivotkey,则向右移动指针low(执行操作++low);否则将low所指记录与数周记录交换

4.重复步骤2和3,直至low和high相等为止。此时low和high的位置即为枢轴在此趟排序中的最终位置,原表被分成两个子表

(二)代码

int Partition(SqList &L,int low,int high)
{
    L.r[0] = L.r[low];    //用子表的第一个记录做枢轴记录
    pivotkey = L.r[low].key;    //枢轴记录关键字保持在pivotkey中
    while(low<high)
    {
        while(low<high && L.r[high].key >= pivotkey)    --high;
        L.r[low] = L.r[high];    //将比枢轴记录小的记录移到低端
        while(low<high && L.r[low].key<=pivotkey)    ++low;
        L.r[high] = L.r[low];    //将比枢轴记录大的记录移到高端
    }
    L.r[low] = L.r[0];    //枢轴记录到位
    return low;    //返回枢轴位置
}

void QSort(SqList &L,int low,int high)
{
    if(low < high)
    {
        pivotloc = Partition(L,low,high);    //将L.r[low...high]一分为二,pivotloc是枢轴位置
        QSort(L,low,pivotloc-1);    //对左子表递归排序
        QSort(L,pivotloc+1,high);    //对右子表递归排序
    }
}

void QuickSort(SqList &L)
{
    QSort(L,1,L.length);
}
View Code

(三)时间复杂度为O(nlog2n)

(四)空间复杂度为O(n)

(五)算法特点

1.不稳定排序

2.排序过程中需要定位表的上界和下界,所以适合用于顺序结构,很难用于链式结构

3.当n较大时,在平均情况下快速排序时所有内部排序方法中速度最块的一种,所以其适合初始记录无序、n较大时的情况

 

选择排序

一、简单选择排序

(一)方法

1.设待排序的记录存放在数组r[1...n]中。第一趟从r[1]开始,通过n-1次比较,从n个记录中选出关键字最小的记录,记为r[k],交换r[1]和r[k]

2.第二趟从r[2]开始,通过n-2次比较,从n-1个记录中选出关键字最小的记录,记为r[k],交换r[2]和r[k]

3.以此类推,第i趟从r[i]开始,通过n-i次比较,从n-i+1个记录中选出关键字最小的记录,记为r[k],交换r[i]和r[k]

4.经过n-1趟,排序完成

(二)代码

void SelectSort(SqList &L)
{
    for(i = 1;i < L.length;i++)
    {//在L.r[i...L.length]中选择关键字最小的记录
        k = i;
        for(j = i+1;j <= L.length;++j)
            if(L.r[j].key < L.r[k].key)    k = j;//k指向此趟排序中关键字最小的记录
        if(k != i)
        {//交换r[i]和r[k]
            t = L.r[i];
            L.r[i] = L.r[k];
            L.r[k] = t;
        }
    }
}
View Code

(三)时间复杂度为O(n2)

(四)空间复杂度为O(1)

(五)算法特点

1.稳定排序

2.可用于链式存储结构

3.激动记录次数较少,当每一记录占用的空间较多时,次方法比直接插入排序快

二、堆排序

(一)方法

1.按对的定义将待排序序列r[1...n]调整为大根堆(这个过程称为建初堆),交换r[1]和r[n],则r[n]为关键字最大的记录

2.将r[1...n-1]重新调整为堆,交换r[1]和r[n-1],则r[n-1]为关键字次大的记录

3.循环n-1次,直到交换了r[1]和r[2]为止,得到了一个非递减的有序序列r[1...n]

(二)代码

void HeapAdjust(SqList &L,int s,int m)
{
    rc = L.r[s];
    for(j = 2*s;j <= m;j *= 2)    //沿key较大的孩子结点向下筛选
    {
        if(j<m && L.r[j].key<L.r[j+1].key)    ++j;    //j为key较大的记录的下标
        if(rc.key >= L.r[j].key)    break;    //rc应插入在位置s上
        L.r[s] = L.r[j];
        s = j;
    }
    L.r[s] = rc;    //插入
}

void CreatHeap(SqList &L)
{//把无序序列L.r[1...n]建成大根堆
    n = L.length;
    for(i = n/2;i > 0;--i)
        HeapAdjust(L,i,n);    //反复调用HeapAdjust
}

void HeapSort(SqList &L)
{//堆顺序表L进行堆排序
    CreatHeap(L);    //把无序序列L.r[1...n]建成大根堆
    for(i = L.length;i > 1;--i)
    {
        x = L.r[1];    //将堆顶记录和当前未经排序子序列L.r[1...i]中最后一个记录互换
        L.r[1] = L.r[i];
        L.r[i] = x;
        HeapAdjust(L,1,i-1);    //将L.r[1...i-1]重新调整为大根堆
    }
}
View Code

(三)时间复杂度为O(nlog2n)

(四)空间复杂度为O(1)

(五)算法特点

1.不稳定排序

2.只能用于顺序结构,不能用于链式结构

3.初始建堆所需的比较次数较多,因此记录较少时不宜采用,因为当记录较多时较快速排序更高效

 

归并排序

(一)方法:

假设初始序列含有n个记录,则可看成时n个有序的子序列,每个子序列的长度为1,然后凉凉归并,得到[n/2]个长度为2或1的有序子序列;再两两归并,……,如此重复,直至得到一个长度为n的有序序列为止

(二)代码

void Merge(RedType R[],RedType &T[],int low,int high)
{//将有序表R[low...high]和R[mid+1...high]归并为有序表T[low...high]
    i = low;
    j = mid + 1;
    k = low;
    while(i<=mid && j<=high)
    {   
        if(R[i].key <= R[i].key)    T[k] = R[i++];
        else T[k] = R[j++];
    }
    while(i <= mid)    T[k++] = R[i++];    //将剩余的R[low...mid]复制到T中
    while(j <= high)    T[k++] = R[j++];    //将剩余的R[j...high]复制到T中
}

void MSort(RedType R[],RedType &T[],int low,int high)
{//R[low...high]归并排序后放入T[low...high]中
    if(low == high)    T[low] = R[low];
     else
    {
        mid = (low + high)/2;    //将当前序列一分为二,求分裂点mid
        MSort(R,S,low,mid);    //对子序列R[low...mid]递归归并排序,结果放入S[low...mid]
        MSort(R,S,mid+1,high);    //对子序列R[mid+1...high]递归归并排序,结果放入S[mid+1...high]
        Merge(S,T,low,mid,high);    //将S[low...mid]和S[mid+1...high]归并到T[low...high]
    }
}

void MergeSort(SqList &L)
{
    MSort(L.r,L.r,1,L.length);
}
View Code

(三)时间复杂度为O(nlog2n)

(四)空间复杂度为O(n)

(五)算法特点

1.稳定排序

2.可用于链式结构,且不需要附加存储空间,但递归是现实仍需开辟相应的递归工作栈

 

外部排序

(一)方法

1.按可用内存大小,将外存上含n个记录的文件分成若干长度为l的子文件或段,依次读入内存并利用有效的内部排序方法对它们进行排序,并将排序后得到的有序子文件重新写入外存,通常称这些有序子文件为归并段或顺串

2.对这些归并段进行逐趟归并,使归并段(有序的子文件)逐渐由小至大,直至得到整个有序文件为止

posted @ 2020-07-12 18:42  繁华声  阅读(216)  评论(0编辑  收藏  举报