zoukankan      html  css  js  c++  java
  • 剑指offer 查找和排序的基本操作:查找排序算法大集合

    重点

    查找算法着重掌握:顺序查找、二分查找、哈希表查找、二叉排序树查找。

    排序算法着重掌握:冒泡排序、插入排序、归并排序、快速排序。

    顺序查找

    算法说明

    顺序查找适合于存储结构为顺序存储或链接存储的线性表。

    算法思想

    顺序查找也称为线形查找,属于无序查找算法。从数据结构线形表的一端开始,顺序扫描,依次将扫描到的结点关键字与给定值k相比较,若相等则表示查找成功;若扫描结束仍没有找到关键字等于k的结点,表示查找失败。

    算法实现

    int sequenceSearch(int a[], int value, int len)
    {
        int i;
        for(i=0; i<len; i++)
            if(a[i]==value)
                return i;
        return -1;
    }

    算法分析

    查找成功时的平均查找长度为:(假设每个数据元素的概率相等) ASL = 1/n(1+2+3+…+n) = (n+1)/2 ;查找不成功时,需要n+1次比较,时间复杂度为O(n);所以,顺序查找的时间复杂度为O(n)


    二分查找

    算法说明

    元素必须是有序的,如果是无序的则要先进行排序操作。

    算法思想

    也称为是折半查找,属于有序查找算法。用给定值k先与中间结点的关键字比较,中间结点把线形表分成两个子表,若相等则查找成功;若不相等,再根据k与该中间结点关键字的比较结果确定下一步查找哪个子表,这样递归进行,直到查找到或查找结束发现表中没有这样的结点。注:折半查找的前提条件是需要有序表顺序存储,对于静态查找表,一次排序后不再变化,折半查找能得到不错的效率。但对于需要频繁执行插入或删除操作的数据集来说,维护有序的排序会带来不小的工作量,那就不建议使用。

    算法实现

    //二分查找,常规版
    int binarySearch1(int a[], int value, int len)
    {
        int low, high, mid;
        low = 0;
        high = len-1;
        while(low<=high)
        {
            mid = low+(high-low)/2;    //防止溢出
            if(a[mid]==value)
                return mid;
            if(a[mid]>value)
                high = mid-1;
            if(a[mid]<value)
                low = mid+1;
        }
        return -1;
    }
     
    //二分查找,递归版
    int binarySearch2(int a[], int value, int low, int high)
    {
        int mid = low+(high-low)/2;
        if(a[mid]==value)
            return mid;
        if(a[mid]>value)
            return BinarySearch2(a, value, low, mid-1);
        if(a[mid]<value)
            return BinarySearch2(a, value, mid+1, high);
    }

    算法分析

    最坏情况下,关键词比较次数为log2(n+1),故时间复杂度为O(log2n);


    冒泡排序

    算法说明

    属于交换类排序,稳定排序

    算法思想

    比较相邻的两个数的大小,将最大的数放在右边,计数器i++;

    继续重复操作1,直到a[n-2]和a[n-1]比较结束,数组a中最大的值已在a[n-1];

    将进行排序的数组长度n减1,重复操作1和操作2,直到n为1,排序完毕。

    算法实现

    void bubbleSort(int* array, int length)
    {
        for (int i = 0; i < length - 1; ++i)
        {
            //bool is_Swap=false;
            for (int j = 0; j < length - 1 - i; ++j)
            {
                if (array[j] > array[j + 1])
                {
                    //is_Swap=true;
                    int temp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = temp;
                    /*
                    交换还可使用如下方式
                    a = a + b;
                    b = a - b;
                    a = a - b;
                    交换还可使用如下方式
                    a=a^b;
                    b=b^a;
                    a=a^b;
                    */
                }
            }
            //if(is_Swap==false)
                //return;
        }
    }

    算法分析

    平均时间复杂度:O(n^2)。最好的情况:如果待排序数据序列为正序,则一趟冒泡就可完成排序,排序的比较次数为n-1次,且没有移动,时间复杂度为O(n)。要实现O(n)的复杂度,代码里需要加一个标志位(Bool变量)。最坏的情况:如果待排序数据序列为逆序,则冒泡排序需要n-1次趟,每趟进行n-i次排序的比较和移动,即比较和移动次数均达到最大值:比较次数=n(n−1)/2=O(n^2),移动次数等于比较次数,因此最坏时间复杂度为O(n^2)。


    插入排序

    算法说明

     属于插入类排序,稳定排序。

    算法思想

     从待排序的数组的第二个元素开始,将其与前面的数进行大小比较,寻找合适的位置并插入,直到全部元素都已插入。

    算法实现

    void insertSort(int* array,int length) 
    {
        int i = 0, j = 0, temp = 0;
        for (i = 1; i < length; ++i) 
        {
            //如果该元素小于前面的元素,大于该元素的元素全部后移一位,
            //直到找到该元素要插入的位置并插入之。
            if (array[i] < array[i-1])
            {
                temp = array[i];
                for (j = i-1; temp < array[j] && j >= 0 ; --j) 
                {
                    array[j+1] = array[j];
                }
                array[j + 1] = temp;
            }
        }
    }

    算法分析

     平均时间复杂度:O(n^2)。最好的情况:当待排序记录已经有序,这时需要比较的次数为n-1=O(n)。最坏的情况:如果待排序记录为逆序,则最多的比较次数为n*(n-1)/2=O(n^2)


    归并排序

    算法说明

     应用较广,稳定排序。

    算法思想

    归并排序是分治法的一个典型的应用,先使每个子序列有序,再使每个子序列间有序。将两个有序子序列合并成一个有序表,称为二路归并。 步骤:首先将有序数列一分二,二分四……直到每个区都只有一个数据,此时每个子序列都可看做有序序列。然后进行合并,每次合并都是有序序列在合并,

    算法实现

    void MergeArray(int* array, int first, int mid, int last, int* temp)
    {
        //将a[first...mid]和a[mid+1...last]合并
        int i = first, j = mid + 1, k = 0;
        int lengthA = mid+1, lengthB = last+1;
        while (i < lengthA&&j < lengthB) 
        {
            if (array[i] < array[j])
                temp[k++] = array[i++];
            else
                temp[k++] = array[j++];
        }
        while (i < lengthA) 
        {
            temp[k++] = array[i++];
        }
        while (j < lengthB) 
        {
            temp[k++] = array[j++];
        }
        for (i = 0; i < k; ++i) 
        {
            array[first + i] = temp[i];
        }
    }
    
    void MergeSort(int* array, int first, int last, int* temp) 
    {
        if (first >= last)
            return;
        int mid = (first + last) / 2;
        MergeSort(array, first, mid, temp);//左边有序
        MergeSort(array, mid + 1, last, temp);//右边有序
        MergeArray(array, first, mid, last, temp);//合并两个有序的子序列
    }

    算法分析

    平均、最好、最坏的时间复杂度都为:O(n*log n)

    可以这样理解:合并需要O(log n)步操作,每步将排好序的子序列合并需要O(n)的操作。那时间复杂度肯定是O(n*log n)。


    快速排序

    算法说明

     在交换类排序算法中,快排是速度最快的。采用分治的思想,不稳定排序。

    算法思想

    从n个元素中选择一个元素作为分区的标准,一般选第一个元素;

    把小于该元素的放在左边,把大于等于该元素的放在右边,中间就放该元素;

    再分别对左右子序列重复操作1和2,直到每个子序列里只有一个元素,排序完毕。

    算法实现

    //版本1
    void QuickSort(int* array,int low,int high) 
    {
        if (low >= high)
            return;
        int left = low;
        int right = high;
        int key = array[left];//选择第一个元素作为区分元素,当然也可以选最后一个元素。
        while (left != right)
        {
            while (left != right&&array[right] >= key)//从右往左,把小于key的元素放到key的左边
                --right;
            array[left] = array[right];
            while (left != right&&array[left] <= key)//从左往右,把大于key的元素放到key的右边
                ++left;
            array[right] = array[left];
        }
        array[left] = key;//此时left等于right
    
        //一分为二,分治思想,递归调用。
        QuickSort(array, low, left - 1);
        QuickSort(array, left + 1, high);
    }

    众所周知,Partition函数不管是在快速排序中,还是在找第K大这类问题中,都有很重要的地位,故而分开写,就有了版本2。

    int Partition(int* array,int left,int right)
    {
        int key = array[left];
        while (left != right)
        {
            while (left != right&&array[right] >= key)//从右往左,把小于key的元素放到key的左边
                --right;
            array[left] = array[right];
            while (left != right&&array[left] <= key)//从左往右,把大于key的元素放到key的右边
                ++left;
            array[right] = array[left];
        }
        array[left] = key;
        return left;//返回区分函数
    }
    
    //快排主函数
    void quicksort(int* arr, int left, int right)
    {
        if(left< right)
        {
            int middle = mypartition(arr, left, right);
            quicksort(arr, left, middle-1);
            quicksort(arr, middle+1, right);
        }
    }

    算法分析

    平均时间复杂度:O(n*log n)。原因:快排是将数组一分为二到底,所以需要O(log n)次此操作,每次操作需要排序n次,所以,大多数情况下,时间复杂度都是O(n*log n)。最好的情况:是每趟排序结束后,每次划分使两个子文件的长度大致相等,时间复杂度为O(n*log n)。最坏的情况:是待排序元素已经排好序。第一趟经过n-1次比较后第一个元素保持位置不变,并得到一个n-1个元素的子序列;第二趟经过n-2次比较,将第二个元素定位在原来的位置上,并得到一个包括n-2个元素的子序列,依次类推,这样总的比较次数是:n(n-1)/2=O(n^2)。


  • 相关阅读:
    UVALive 5983 MAGRID DP
    2015暑假训练(UVALive 5983
    poj 1426 Find The Multiple (BFS)
    poj 3126 Prime Path (BFS)
    poj 2251 Dungeon Master 3维bfs(水水)
    poj 3278 catch that cow BFS(基础水)
    poj3083 Children of the Candy Corn BFS&&DFS
    BZOJ1878: [SDOI2009]HH的项链 (离线查询+树状数组)
    洛谷P3178 [HAOI2015]树上操作(dfs序+线段树)
    洛谷P3065 [USACO12DEC]第一!First!(Trie树+拓扑排序)
  • 原文地址:https://www.cnblogs.com/parzulpan/p/11258491.html
Copyright © 2011-2022 走看看