查找与排序

查找算法

顺序查找

  • 顺序查找又称线性查找。它的过程为:从查找表的最后一个元素开始逐个与给定关键字比较,若某个记录的关键字和给定值比较相等,则查找成功,否则,若直至第一个记录,其关键字和给定值比较都不等,则表明表中没有所查记录查找不成功,它的缺点是效率低下。

二分查找

  • 简介
    基本思想是将n个元素分成大致相等的两部分,取a[n/2]与x做比较,如果x=a[n/2],则找到x,算法中止;如果x < a[n/2],则只要在数组a的左半部分继续搜索x,如果x > a[n/2],则只要在数组a的右半部搜索x。
    二分查找的时间复杂度为O(logn)
  • 实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    int BinSearch(int low,int high,int target)
    {
    while(low<=high){
    int mid=(low+high)/2;
    if(target==arr[mid])return mid;
    else if(arr[mid]>target){
    high=mid-1;
    }
    else low=mid+1;
    }
    return -1;
    }

    排序算法

    冒泡排序

  • 简介
    基本思想是:设排序序列的记录个数为n,进行n-1次遍历,每次遍历从开始位置依次往后比较前后相邻元素,这样较大的元素往后移,n-1次遍历结束后,序列有序。
  • 实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //冒泡排序:从小到大
    void BubbleSort(int arr[],int len)
    {
    int flag=true;
    for(int i=0;i<len-1;i++){//len-1次冒泡
    if(!flag)break;
    flag=false;
    for(int j=0;j<len-i-1;j++){//去除最后一个已到位的
    if(arr[j]>arr[j+1]){
    swap(arr[j+1],arr[j]);
    flag=true;
    }
    }
    }
    return;
    }
  • 分析
    最佳情况下冒泡排序只需一次遍历就能确定数组已经排好序,不需要进行下一次遍历,所以最佳情况下,时间复杂度为**O(n)
    最坏情况下冒泡排序需要n-1次遍历,第一次遍历需要比较n-1次,第二次遍历需要n-2次,…,最后一次需要比较1次,最差情况下时间复杂度为
    O(n^2)**。

    简单选择排序

  • 简介
    设排序序列的记录个数为n,进行n-1次选择,每次在n-i+1(i = 1,2,…,n-1)个记录中选择关键字最小的记录作为有效序列中的第i个记录。
  • 实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //选择排序,从小到大
    void SelectSort(int arr[],int len)
    {
    for(int i=0;i<len-1;i++){
    int mini=i;
    for(int j=i+1;j<len;j++){
    if(arr[mini]>arr[j]){
    mini=j;
    }
    }
    swap(arr[i],arr[mini]);
    }
    }
  • 分析
    进行比较操作的时间复杂度为 O(n^2) ,进行移动操作的时间复杂度为 O(n) 。总的时间复杂度为O(n^2)

    直接插入排序

  • 简介
    思想:是将一个记录插入到已排好序的有序表中,从而得到一个新的、记录数增1的有序表。
  • 实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //直接插入排序,从小到大
    void DInsertSort(int arr[],int len)
    {
    int now=0;
    for(int i=1;i<len;i++){
    now=arr[i];
    int j=i-1;
    for(;j>=0;j--){
    if(arr[j]<now){
    break;
    }
    arr[j+1]=arr[j];
    }
    arr[j+1]=now;
    }
    }
  • 分析
    最好情况下,当待排序序列中记录已经有序时,则需要n-1次比较,不需要移动,时间复杂度为O(n) 。最差情况下,当待排序序列中所有记录正好逆序时,则比较次数和移动次数都达到最大值,时间复杂度为O(n^2) 。平均情况下,时间复杂度为**O(n^2)**。

    归并排序

  • 简介
    归并排序是分治法的一个典型应用,它的主要思想是:将待排序序列分为两部分,对每部分递归地应用归并排序,在两部分都排好序后进行合并。
  • 实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    //归并排序:从小到大
    //#define Max 1000
    int Merge(int A[],int p,int q,int r)
    {
    int n1=q-p+1;
    int n2=r-q;
    int i=0,j=0;
    //用2个数组分别存放目标A[]数组
    //p~q与q+1~r的数
    int L[n1+1],R[n1+1];
    for(i=0;i<n1;i++){
    L[i]=A[p+i];
    }
    L[i]=Max;
    for(j=0;j<n2;j++){
    R[j]=A[q+j+1];
    }
    R[j]=Max;
    i=0;j=0;
    //合并L[]与R[]数组
    for(int k=p;k<=r;k++){
    if(L[i]<=R[j]){
    A[k]=L[i];
    i++;
    }
    else{
    A[k]=R[j];
    j++;
    }
    }
    return 1;
    }

    int MergeSort(int A[],int p,int r)
    {
    int q=0;
    if(p<r){
    q=(p+r)/2;
    MergeSort(A,p,q);
    MergeSort(A,q+1,r);
    Merge(A,p,q,r);
    }
    return 1;
    }
    //MergeSort(array,0,len-1);
  • 分析
    时间复杂度为O(nlogn)

    快速排序

  • 简介
    主要思想是:在待排序的序列中选择一个称为主元的元素,将数组分为两部分,使得第一部分中的所有元素都小于或等于主元,而第二部分中的所有元素都大于主元,然后对两部分递归地应用快速排序算法。
  • 实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    //快速排序:从小到大
    //实现对子数组A[p,r]的原址重拍
    int PARTITION(int A[],int p,int r)
    {
    int x=A[r];
    //i作为比x小的位置标记
    int i=p-1;
    int j;
    for(j=p;j<r;j++)
    {
    if(A[j]<=x){
    //使i+1到j之前严格大于A[r]
    i=i+1;
    swap(&A[i],&A[j]);
    }
    }
    i=i+1;
    swap(&A[i],&A[r]);
    return i;
    }

    void QUICKSORT(int A[],int p,int r)
    {
    if(p<r){
    int x=PARTITION(A,p,r);
    QUICKSORT(A,p,x-1);
    QUICKSORT(A,x+1,r);
    }
    }
    //QUICKSORT(array,0,len-1);
  • 分析
    最差情况下算法需要(n-1)+(n-2)+…+1= O(n^2) 时间;
    最佳情况下,每次主元将数组划分为规模大致相等的两部分,时间复杂度为**O(nlogn)**。

    堆排序

  • 简介
    堆性质(小根堆):(1) ki <= k(2i)且 ki <= k(2i+1) (1 ≤ i≤ n/2),即完全二叉树中所有的非终端节点的值均不大于(或不小于)其左右孩子节点的值。
    主要思想是:给定一个待排序序列,首先经过一次调整,将序列构建成一个大根堆,此时第一个元素是最大的元素,将其和序列的最后一个元素交换,然后对前n-1个元素调整为大根堆,再将其第一个元素和末尾元素交换,这样最后即可得到有序序列。
  • 实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    //堆排序:小到大
    void MAX_HEAPIF(int A[],int i)
    {
    int x=2*(i+1);
    int l=x-1;
    int r=x;
    int largest=i;
    if(l<Heap_Size&&A[i]<A[l]){
    largest=l;
    }
    if(r<Heap_Size&&A[largest]<A[r]){
    largest=r;
    }
    if(largest!=i){//根节点比他的孩子结点小,需要交换
    int f=A[i];
    A[i]=A[largest];
    A[largest]=f;

    //为保持以孩子结点为根节点的堆为最大堆
    MAX_HEAPIF(A,largest);
    }
    }

    //将最原始的数组变为最大堆
    //需要从最底下的非叶子结点往上建
    void BUILD_MAX_HEAP(int A[],int n)
    {
    int node=n/2;
    for(int i=node-1;i>=0;i--){
    MAX_HEAPIF(A,i);
    }
    }

    //堆排序算法
    void HEAP_SORT(int A[],int n)
    {
    BUILD_MAX_HEAP(A,n);
    //建一个最大堆之后,每次将根节点与Heap_size最后一个结点置换,再继续建最大堆
    //直到Heap_size为1
    while(Heap_Size>1){
    int f=A[Heap_Size-1];
    A[Heap_Size-1]=A[0];
    A[0]=f;

    Heap_Size--;

    MAX_HEAPIF(A,0);
    }
    }
    //Heap_Size=len;
    //HEAP_SORT(array,len);
  • 分析
    由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。堆排序时间复杂度也为**O(nlogn)**,空间复杂度为O(1)。它是不稳定的排序方法。与快排和归并排序相比,堆排序在最差情况下的时间复杂度优于快排,空间效率高于归并排序。
    二叉树的性质
    各种常用排序算法
    常见数据结构与算法整理

桶排序

非比较排序;[0,MAX]格子的桶;

基数排序

桶排序的延伸,预处理成数位相同,从个位开始到最高位,依次桶排序