排序

这里写图片描述

lg(M/N)=lg(M)-lg(N)

这里写图片描述

有序表:

  1. 按键检索
  2. 按键插入

插入排序:
每次迭代将第1个未排序的元素插入到有序部分的合适部分。
即将元素依次与有序部分的元素依次进行比较直到找到合适的位置

template<typename ListEntry>
void InsertSort(OrderList<ListEntry> &l1)
{
    int size = l1.size();
    if (size == 0) {
        cout << "Error:" << __FILE__ << ": in function "
            << __func__ << " at line " << __LINE__ << endl
            << "OrderList is empty" << endl;
        return;
    }
    int be_sort = 1;
    ListEntry data, item;
    for (int i = 1;i < size;++i) {
        l1.retrieve(i, item);
        int t = be_sort - 1;
        l1.retrieve(t, data);
        //需要移动
        if (data > item) {
            l1.remove(i);
            //从后向前比较
            int j;
            for (j = t;j >0;j--) {
                l1.retrieve(j - 1, data);
                //继续向前寻找
                if (data > item) continue;
                    //已经找到合适的位置
                    else break;
                }
            //防止元素无法移动到第1个前面
            l1.insert(j, item);
        }
        ++be_sort;
    }
}

这里写图片描述

最佳情况:
表已经是有序的,所以每次循环只进行1次比较。所以n个元素一共进行n-1次比较。可得时间复杂度为O(n)
最差情况:
每个元素都要进行n-1次比较
这里写图片描述

选择排序:
首先找出关键词最大的元素并与相应位置上的元素交换位置,在每次遍历未排序的部分,找出第n大的元素。

每次迭代都有元素放在最终的位置上。
在不同迭代的寻找最大元素的过程中可能会造成重复的判断。

template<typename Entry = int, int size>
void SelectSort(array<Entry, size> &a1)
{
    int size = a1.size(), st = 0;
    if (size <2) {
        cout << "Error:" << __FILE__ << ": in function "
            << __func__ << " at line " << __LINE__ << endl
            << "array doesn't need to sort: size=" << size << endl;
        return;
    }
    int end, max;
    for (end = size;end >1;--end) {
        max = a1[0];
        //寻找未排序的最大值
        for (int i = 1;i < end;++i) {
            if (a1[i] > max) {
                max = a1[i];
                st = i;
            }
        }
        //依次与最后的元素交换
        swap(a1[end - 1], a1[st]);
    }
}

这里写图片描述

希尔排序:
对插入排序进行优化,提高增量。通过不同的迭代增量(尽量选择不是倍数的值,避免上一趟比较过的键在下一趟被重复比较)对表进行优化,最后增量为1进行检查。随机性较大,难以计算复杂度。
这里写图片描述

template<typename Entry = int, int size>
void ShellSort(array<Entry, size> &a)
{
    int size = a.size();
    if (size <2) {
        cout << "Error:" << __FILE__ << ": in function "<< __func__ << " at line " << __LINE__ << endl<< "array doesn't need to sort: size=" << size << endl;
        return;
    }
    int d = size;//子表中每2个元素的距离为d-1
    do {
        //使增量不互为倍数且最后为1
        d = d / 3 + 1;
        for (int i = 0;i<d;++i)
        //对每个子表的元素进行比较
        for (int j = i;j + d< size;j += d)
            if (a[j] > a[j + d])
                swap(a[j], a[j + d]);
        } while (d > 1);//防止d走不出循环
}

归并排序:先逐次平分成不同的序列再排好序
随着递归平分,然后合并(开始比对)
这里写图片描述

template<typename Entry = int, int size>
void MergeSort(array<Entry, size> &a)
{
    int size = a.size();
    assist_mergesort(a, 0, size - 1);
}

template<typename Entry = int, int size>
void assist_mergesort(array<Entry, size> &a, int beg, int end)
{
    //只有1个时直接返回
    if (beg < end) {
        //将序列平分成2半
        int half = (beg + end) / 2;
        assist_mergesort(a, beg, half);
        assist_mergesort(a, half + 1, end);
        //递归返回后按序合并
        merge(a, beg, half, end);
    }
}
//复制2个子表的元素至2个新数组,比较后将元素重新插入原表
template<typename Entry = int, int size>
void merge(array<Entry, size> &a, int beg, int half, int end)
{
    //注意复制区间的起始位置
    auto iter1 = a.begin();
    iter1 += beg;
    //创建2个新子表
    int d1 = half - beg + 1, d2 = end - half;
    auto iter2 = iter1 + d1;
    auto iter3 = iter2 + d2;
    //复制区间左闭右开
    vector<int>a1(iter1, iter2);
    vector<int>a2(iter2, iter3);
    int i = beg,i1 = 0, i2 = 0;
    //从2个子表中按大小顺序复制元素形成新的序列
    while (i1 < d1&&i2 < d2) 
        if (a1[i1] > a2[i2])
            a[i++] = a2[i2++];
        else
            a[i++] = a1[i1++];  
    //如果有子表存在剩余,直接插入    
    if (i1 < d1)
        for (;i1<d1;++i1)
            a[i++] = a1[i1];
    if (i2 < d2)
        for (;i2<d2;++i2)
            a[i++] = a2[i2];
}

链式
这里写图片描述

void MergeSort(OrderList* &l1)
{
  if(l1!=nullptr&&l1->next!=nullptr){
    auto half=divide_from(l1);
    MergeSort(l1);
    MergeSort(half);
    l=merge(l1,half);
  }
}

void MergeSort(OrderList* &begin,OrderList* &end)
{
  OrderList node;
  OrderList* pre=node;
  while(begin!=nullptr&&end!=nullptr){
     if(begin->entry<second->entry){
        pre->next=begin;
        pre=begin;
        begin=begin->next;
     }
     else{
        pre->next=end;
        pre=end;
        end=end->next;
     }
     if(begin==nullptr) pre->next=end;
     else pre->next=begin;
     return node.next;
  }
}

由比较树可以看出,归并算法是从树叶向根节点进行排序。每一层的比较总次数最多为n-1,由于n可能为奇数,所以最多有lg(n)层。
例如2个子表分别为123和456,则只需要比较3次;135和246需要比较5次。
所以比较次数≤nlg(n)(每层的比较次数*层数)

快速排序:

  • 每次分割根据某个键值分成小于和大于的2部分
  • 随机选择1个元素作为比较对象并将其放在表的最后,比较完后放回中间。所以每次迭代完成后被用作比较的键会被放到正确的位置上。
  • 小于该元素的放在前面;大于放在后面
  • 元素小于时下标i和j向后移动来标记每个部分的最后1个元素

  • 可以看做是升级版的选择排序,每次都有1个元素移动到正确的位置;但是在选择排序中其他剩余的元素依然是无序的,快速排序却初步将剩余的元素划分成大于和小于2个元素。

  • 快速排序的缺点在于无法控制子表的长度
  • 每次迭代比较n-1次。所以比较次数C(n)=n-1+C(r)+C(n-r-1)
  • 最坏情况:每次选择的键为最值,会导致1个子表为0,所以C(n)max=n-1+n-2+ ……+1=n^2/2+n/2=O(n^2)
  • 平均性能:
    设每次partition函数返回的左边分布的长度为p,
    C(n,p)为第一次返回p后总的比较次数
    有C(n,p)=n-1+C(p-1)+C(n-p)
    这里写图片描述
  • 最佳性能:
    为了让总的比较次数越小,则需要层数越小。递归的结束条件为范围中只有1个元素,所以每条路径的叶节点都是1。所以为了减少层数,最理想的情况为每个子问题的划分为n/2,使每条路径最快的减小到1。
template<typename Entry = int, int size>
void QuickSort(array<Entry, size> &a)
{
    int size = a.size();
    assist_quicksort(a, 0, size - 1);
}

template<typename Entry = int, int size>
void assist_quicksort(array<Entry, size> &a, int beg, int end)
{
    if (beg < end) {
        int half = parttition(a, beg, end);
        //中位数无需排序
        assist_quicksort(a, beg, half - 1);
        assist_quicksort(a, half + 1, end);
    }
}

template<typename Entry = int, int size>
int parttition(array<Entry, size> &a, int beg, int end)
{
    int t = a[end];//将最后1个元素作为划分的依据
    int end1 = beg - 1;//同时作为第1段的终止位置和第2段的起始位置
    int i = beg;
    for (;i < end;++i)
        if (a[i]< t) {
            ++end1;
            swap(a[i], a[end1]);
        }
    //将作为比较的数放回中间
    swap(a[end1 + 1], a[end]);
    return end1 + 1;
}

这里写图片描述
这里写图片描述

最大堆:

  • 父节点大于子节点
  • 同一父节点之间没有强制的大小顺序关系
  • 不需要使用树的结构,只是为了表达更为直观
    这里写图片描述

应用:

堆排序:

  • 提出末尾的元素,将头结点放到末尾
  • 依次找出子节点中的最大值向上移动
  • 将末尾元素放在堆的最后

堆的建立:

  1. 先将表中的元素放到堆中。由后向前遍历表的前半部分(最后一层的节点个数乘2减1等于总个数父),用父节点与其子节点相比对,将大的放在前面。
  2. 将堆的顶点取出并放到表的末尾,通过比较移动其子节点。
    先比较空位的所有2个子节点找出最大值,如果被替换的节点大于等于该值,停止遍历。否则向下1层继续遍历。
template<typename T>
void build_heap(vector<T>& v)
{
  int count=v.size();
  for(int i=count/2-1;i>=0;--i)
     heap_insert(v,i,count-1);
}

//heap_insert还需要继续用于堆排序中堆的维护,
//所以在参数中需要加入e标识未排序的末尾
template<typename T>
void heap_insert(vector<T>& v,int i,int e)
{
  int j=2*i+1;//左子节点   
  while(j<=e)
  {
    //找出左右子节点的最大值
    if( j<(v.size()-1) && v[j]<v[j+1] ) ++j;
    //是否需要调整
    if(v[i]<v[j]) 
    {
      swap(v[i],v[j]); 
      i=j;
      j=2*j+1;
    }
    //由于调整后可能会导致子树违背最大堆的性质
    //所以需要继续向下进行检查
    else break;
  }     
}

这里写图片描述
这里写图片描述

template<typename T>
void heap_sort(vector<T>& v)
{
  build_heap(v);
  for(int unsort=v.size()-1;unsort>0;--unsort)
  {
     swap(v[0],v[unsort]);
     heap_insert(v,0,unsort-1);
  }
}

性能分析:
在第一阶段:调用n次heap_insert,复杂度为O(n)
在第二阶段:进行交换后,v[0]的值都要被交换到未排序的最后面。
当树的节点总个数为n时
这里写图片描述
每个被遍历的元素的比较次数为:
这里写图片描述
每次循环比较2次,所有元素的循环次数为:
这里写图片描述
优先队列:(参考STL的priority_queue,包装了Heap)

  • 插入:放到堆的最后面
  • 取出:使用pop_back()后再进行调整

二叉比较树:

  • 高度:最大次数
  • 外部路径/叶节点:平均次数
  • 外部路径长度E:
    遍历从根到树中每个树叶所经历的分支数(不是节点数)
  • 内部路径长度I:
    遍历从根到树中每个非树叶所经历的分支数(不是节点数)
  • E=I+2q(非树叶的顶点数目)
  • 由于树每层的最大结点数是其上一层的2倍。所以第t层(t>=0)的最大节点数为2^t。所以当第t层有k个节点,t≥lg k。

排序算法的下限:
由于不同的排列顺序有n!种。——》树叶数至少为n!——》h至少为lg(n!)=Ω(nlgn)——》使用键比较的排序至少进行Ω(nlgn)次比较

这里写图片描述

这里写图片描述

选择排序的特点:

  • 移动高效
  • 比较冗余

    插入排序:

  • 牺牲移动效率减少比较次数(树的分支树更少,虽然复杂度都是O(n))
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值