十大基础实用算法之归并排序和二分查找

归并排序

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

算法步骤:

1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列

2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置

3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置

4. 重复步骤3直到某一指针达到序列尾

5. 将另一序列剩下的所有元素直接复制到合并序列尾

用分治策略解决问题分为三步:分解、解决、合并。也即:将原问题划分成n个规模较小而结构与原问题相似的子问题;
递归地解决这些子问题,然后再合并其结果,得到原问题的解。此处n=2。每次都把原问题划分为两个子问题。

归并排序的伪代码(来自算法导论)

合并排序伪代码(使用哨兵):
merge(A,p,q,r)://合并算法,[p,q],[q+1,r]
    n1 <—— q-p+1
    n2 <—— r-q
    create array L[0,n1] and R[0,n2]
    for i <—— 0 to n1-1
        do L[i] <—— A[p+i]
    for j <—— 0 to n2-1
        do R[j] <—— A[q+j+1]
    L[n1] <—— +∞
    R[n2] <—— +∞
    i <—— 0
    j <—— 0
    for k i <—— p to r
        do if L[i]<=R[j]
            then A[k]  <—— L[i]
                 i <—— i+1
           else A[k] <—— R[j]
                 j <—— j+1

//通过调用merge完成排序:
merge_sort(A,p,r):
    if p<r
       then q <—— [(p+r)/2] //向下取整
          merge_sort(A,p,q) //分治
          merge_sort(A,q+1,r)
          merge(A,p,q,r)    //合并结果

归并排序实现

#include <stdio.h>
#include <stdlib.h>
#define MAX_INT  ~(1<<31)//最大整数
//arr[p,q]  arr[q+1,r]
void merge(int *arr,int p,int q,int r)
{
    if(arr==NULL)
        return;
    int n1=q-p+1;
    int n2=r-q;
    int *L=(int*)malloc((n1+1)*sizeof(int));//申请新空间用于暂时存储元素
    int *R=(int*)malloc((n2+1)*sizeof(int));
    int i,j;//这里直接申请一个r-p+1长度的数组,在临时数组中排序后拷贝回原数组
    for(i=0;i<n1;++i)
        L[i]=arr[p+i];
    for(j=0;j<n2;++j)
        R[j]=arr[q+j+1];
    //哨兵元素赋值
    L[n1]=MAX_INT;
    R[n2]=MAX_INT;
    int k;
    i=0,j=0;
    for(k=p;k<=r;++k){
        if(L[i]<=R[j])
            arr[k]=L[i++];
        else
            arr[k]=R[j++];
    }//最后可能有一个数组有元素剩余,这里使用一个哨兵(MAX_INT)可以在一次循环中完成全部复制
    free(L);
    free(R);
}
void merge_sort(int *arr,int p,int r)
{
    if(p<r){
        int q=(r+p)/2;
        merge_sort(arr,p,q);//分治
        merge_sort(arr,q+1,r);
        merge(arr,p,q,r);//合并结果
    }

}
 int main()
{
    int arr[8]={32,3,4,5,6,7,9,106};
    merge_sort(arr,0,7);
    for (int i=0;i<8;i++)
    printf("%d ",arr[i]);
    system("pause");
}

二分查找算法

二分查找算法是一种在有序数组中查找某一特定元素的搜索算法。搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜素过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。折半搜索每次把搜索区域减少一半,时间复杂度为Ο(logn)

二分查找可以解决(预排序数组的查找)问题:只要数组中包含T(即要查找的值),那么通过不断缩小包含T的范围,最终就可以找到它。其算法流程如下:

  • 1、一开始,范围覆盖整个数组。
  • 2、将数组的中间项与T进行比较,如果T比数组的中间项要小,则到数组的前半部分继续查找,反之,则到数组的后半部分继续查找。
  • 3、如此,每次查找可以排除一半元素,范围缩小一半。就这样反复比较,反复缩小范围,最终就会在数组中找到T,或者确定原以为T所在的范围实际为空。

对于包含N个元素的表,整个查找过程大约要经过log(2)N次比较。

//首先要把握下面几个要点:
//right = n-1 => while(left <= right) => right = middle-1;
//right = n   => while(left <  right) => right = middle;
//middle的计算不能写在while循环外,否则无法得到更新。

int BinarySearch(int array[], int n, int value)
{
    int left = 0;
    int right = n - 1;
    //如果这里是int right = n 的话,那么下面有两处地方需要修改,以保证一一对应:
    //1、下面循环的条件则是while(left < right)
    //2、循环内当 array[middle] > value 的时候,right = mid

    while (left <= right)  //循环条件,适时而变
    {
        int middle = left + ((right - left) >> 1);  /*防止溢出,移位也更高效。同时,每次循环都需要更新。mid = left + (right-left)/2这样写可以有效避免right + left 溢出。如果写成mid = (left + right)/2可能存在溢出*/

        if (array[middle] > value)
        {
            right = middle - 1;  //right赋值,适时而变
        }
        else if(array[middle] < value)
        {
            left = middle + 1;
        }
        else
            return middle;
        //可能会有读者认为刚开始时就要判断相等,但毕竟数组中不相等的情况更多
        //如果每次循环都判断一下是否相等,将耗费时间
    }
    return -1;
}

以上就是本次介绍的归并排序和二分查找。

十大基础实用算法之归并排序和二分查找

时间: 2024-10-12 16:16:11

十大基础实用算法之归并排序和二分查找的相关文章

十大基础实用算法之快速排序和堆排序

快速排序是由东尼·霍尔所发展的一种排序算法.在平均状况下,排序 n 个项目要Ο(n log n)次比较.在最坏状况下则需要Ο(n2)次比较,但这种状况并不常见.事实上,快速排序通常明显比其他Ο(n log n) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来. 快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists). 算法步骤: 1 从数列中挑出一个元素,称为 "基准"(pi

十大基础实用算法之迪杰斯特拉算法、最小生成树和搜索算法

迪杰斯特拉(Dijkstra)算法是典型最短路径算法,用于计算一个节点到其他节点的最短路径. 它的主要特点是以起始点为中心向外层层扩展(广度优先搜索思想),直到扩展到终点为止. 基本思想 通过Dijkstra计算图G中的最短路径时,需要指定起点s(即从顶点s开始计算). 此外,引进两个集合S和U.S的作用是记录已求出最短路径的顶点(以及相应的最短路径长度),而U则是记录还未求出最短路径的顶点(以及该顶点到起点s的距离). 初始时,S中只有起点s:U中是除s之外的顶点,并且U中顶点的路径是"起点s

十大基础实用算法之寻找最小(最大)的k个数-线性查找算法

例如:输入1,2,3,4,5,6,7,8这8个数字,则最小的4个数字为1,2,3,4. 思路1:最容易想到的方法:先对这个序列从小到大排序,然后输出前面的最小的k个数即可.如果选择快速排序法来进行排序,则时间复杂度:O(n*logn) 注:针对不同问题我们应该给出不同的思路,如果在应用中这个问题的规模不大,或者求解前k个元素的频率很高,或者k是不固定的.那么我们花费较多的时间对问题排序,在以后是使用中可以线性时间找到问题的解,总体来说,那么思路一的解法是最优的. 思路2:在思路1的基础上更进一步

十大基础实用算法之动态规划

动态规划(Dynamic programming)是一种在数学.计算机科学和经济学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法. 动态规划常常适用于有重叠子问题和最优子结构性质的问题,动态规划方法所耗时间往往远少于朴素解法. 动态规划背后的基本思想非常简单.大致上,若要解一个给定问题,我们需要解其不同部分(即子问题),再合并子问题的解以得出原问题的解. 通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量: 一旦某个给定子问题的解已经算出,则将

十大基础实用算法之深度优先搜索和广度优先搜索

深度优先搜索算法(Depth-First-Search),是搜索算法的一种.它沿着树的深度遍历树的节点,尽可能深的搜索树的分支.当节点v的所有边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点.这一过程一直进行到已发现从源节点可达的所有节点为止.如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止.DFS属于盲目搜索. 深度优先搜索是图论中的经典算法,利用深度优先搜索算法可以产生目标图的相应拓扑排序表,利用拓扑排序表可以方便的解决很多相

程序员必须知道的10大基础实用算法及其讲解

程序员必须知道的10大基础实用算法及其讲解 原文出处: cricode 算法一:快速排序算法 快速排序是由东尼·霍尔所发展的一种排序算法.在平均状况下,排序 n 个项目要Ο(n log n)次比较.在最坏状况下则需要Ο(n2)次比 较,但这种状况并不常见.事实上,快速排序通常明显比其他Ο(n log n) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构 上很有效率地被实现出来. 快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子

程序员必知的10大基础实用算法

    算法一:快速排序算法 快速排序是由东尼·霍尔所发展的一种排序算法.在平均状况下,排序 n 个项目要Ο(n log n)次比较.在最坏状况下则需要Ο(n2) 次比较,但这种状况并不常见.事实上,快速排序通常明显比其他Ο(n log n) 算法更快,因为它的内部循环(inner loop)可以在大部分的 架构上很有效率地被实现出来. 快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists). 算法步骤: 1 从数列中挑出一个元

《转》程序员必须知道的10大基础实用算法及其讲解

来源: Cricode  发布时间: 2014-06-19 08:27  阅读: 2018 次  推荐: 8   原文链接   [收藏] 算法一:快速排序算法 快速排序是由东尼·霍尔所发展的一种排序算法.在平均状况下,排序n个项目要Ο(nlogn)次比较.在最坏状况下则需要Ο(n2)次比较,但这种状况并不常见.事实上,快速排序通常明显比其他Ο(nlogn)算法更快,因为它的内部循环(innerloop)可以在大部分的架构上很有效率地被实现出来. 快速排序使用分治法(Divideandconque

堆排序算法---《程序员必须知道的10大基础实用算法及其讲解》

> 原帖地址:http://www.oschina.net/question/1397765_159365 快速排序算法的基本特性: 时间复杂度:O(N * logN) 堆排序为不稳定排序,不适合记录较少的排序. var arr = [], count = 100, i = 0, parentIndex, exeCount = 0, startTime = + new Date(), stackSort = function(a){ if(a.length === 1) return a; va