数据结构05-排序和查找

1:排序算法分为如下5类:

  1. 插入排序:普通插入排序,shell排序等;
  2. 选择排序:普通选择排序,堆排序;
  3. 交换排序:冒泡法,快速排序;
  4. 归并排序:
  5. 基数排序。

待排数据为:9,6,2,3,10,有小到大排列;下面来实现上面的各种排序算法

2:插入排序(希尔排序)

基本插入排序:每次将一个待排序的数据元素,插入到前面已经排好序的数列中的适当位置,使数列依然有序;直到待排序数据元素全部插入完为止。

稳定排序:指待排序序列中可能存在两个或两个以上关键字相等的记录。排序前的序列中Ri领先于Rj(即i<j)。若在排序后的序列中Ri仍然领先于Rj,则称所用的方法是稳定的。比如序列:3,6,1a,2,1b,8,4,7,其中1a和1b的值都是1,为了区别,1a在1b的前面。如果排序之后,1a依然在1b的前面,那么就是稳定排序,否则就是非稳定排序。

//基本插入排序
//基本插入排序的时间复杂度为O(n^2),是一种稳定排序算法
void insert_sort(int a[], int n) {
    int i, j, tmp;
    
    for(int i=1; i<n; i++) {
        tmp = a[i];
        j = i - 1;
        
        while(tmp < a[j]) {//当tmp大于等于a[j]时不用交换
            a[j+1] = a[j];
            j--;
        }
        
        a[j+1] = tmp;
    }
}

希尔排序:是一种经过改进的插入排序算法。

希尔排序基本思想:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比前两种方法有较大提高。具体做法:首先确定一组增量d0,d1,d2,d3,...,dt-1()其中n>d0>d1>...>dt-1=1),对于i =0,1,2,...,t-1,依次进行下面的各趟处理:根据当前增量di将n个元素分成di个组,每组中元素的下标相隔为di;再对各组中元素进行直接插入排序。

//希尔排序的实现算法:
//希尔排序的复杂度为:O(n^1.25),它不是稳定排序。
void shellsort(int a[], int n) {
     int i, j, gap, tmp;
     gap = n/2;

     while (gap > 0) {
         for (i = gap + 1; i <= n; i++) {
              j = i - gap;

              while (j > 0) {
                   if (a[j] > a[j+gap]) {
                       tmp = a[j];
                       a[j] = a[j+gap];
                       a[j+gap] = a[j];
                   } else {
                       j = 0;
                   }
              }
         }

         gap /= 2;
     }
}

3:选择排序(堆排序)

选择排序:每一次都是从待排序的数据元素中选出最小(或最大)的一个元素,顺序放在已排好序的数列的最后,直到全部待排序的数据元素排完。

//简单选择排序
//时间复杂度为O(n^2),它是不稳定排序。
void selectsort(int a[], int n) {
       int i, j, k, tmp;

       for (i = 0; i < n-1; i++) {
              k = i;

              for (j = i + 1; j < n; j++) {
                     if (a[j] < a[k])
                            k = j;
              }

              if (k != i) {
                     tmp = a[i];
                     a[i] = a[k];
                     a[k] = tmp;
              }
       }
}

堆排序:是一树形选择排序,在排序过程中,将R[1..N]看成是一颗完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系来选择最小的元素。

//堆排序
//时间复杂度为O(nlogn),是不稳定排序
void sfilter(int a[], int l, int m) {
     int i, j x;
     i = l;
     j = 2 * i;
     x = a[i];

     while ( j <= m) {
         if (j < m && a[j] < a[j+1])
              j++;

         if (x < a[j]) {
              a[i] = a[j];
              i = j;
              j = 2 * i;
         } else {
              j = m + 1;
         }
     }

     a[i] = x;
}

void heapsort(int a[], int n) {
     int i, w;

     for (i = n/2; i >= 1; i--)
         sfilter(a, i, n);

     for (i = n; i >= 2; i--) {
         w = a[i];
         a[i] = a[1];
         a[1] = w;
         sfilter(a, 1, i - 1);
     }
}

4:交换排序(冒泡排序/快排)

交换排序:两两比较待排序记录的关键字,发现两个记录的次序相反时即进行交换,直到没有反序的记录为止。

首先来看著名的交换排序算法:冒泡法。冒泡法的排序思想是:从第n个元素(a[n-1])开始,扫描数组,比较相邻两个元素,如果次序相反则交换。如此反复,直到没有任何两个违反相反原则的元素

//冒泡排序:
//时间复杂度为O(n^2),它是稳定排序
void bubble_sort(int a[], int n) {
       int i, j, tmp;

       for (i = 0; i < n-1; i++) {
              for (j = n-1; j >= i+1; j--) {
                     if (a[j] < a[j-1]) {
                            tmp = a[j];
                            a[j] = a[j-1];
                            a[j-1] = tmp;
                     }
              }
       }
}

快速排序:在当前无序区R[1..H]中任取一个数据元素作为比较的“基准”(不妨记为X,R[1]),用此基准将当前无序区划分为左右两个较小的无序区:R[1..I-1]和R[I+1..H],且左边的无序子区中数据元素均小于等于基准元素,右边的无序子区中数据元素均大于等于基准元素,而基准X则位于最终排序的位置上,即R[1..I-1]≤X≤RI+ 1..H,当R[1..I-1]和R[I+1..H]均非空时,分别对它们进行上述的划分过程,直至所有无序子区中的数据元素均已排序为止。

找一个数X(比如头一个元素)做基准,右边比X小的移动到左边,左边比X大的移动到右边,最后空出的位置就是X自己的位置

快速排序的时间复杂度为O(nlogn),最坏情况为O(n^2)。对于大的、乱数序列一般相信是最快的已知排序。待排记录序列按关键字顺序有序时,直接插入排序和起泡排序能达到O(n)的时间复杂度,而对于快速排序而言,这是最不好的情况。对于很小的数组(如N<=20),快速排序不如插入排序好。

void quickSort(int a[],int left,int right) {
   int i,j,temp;
   i=left;
   j=right;
   temp=a[left];

   if(left>right)
      return;

   while(i<j) {/*找到最终位置*/ 
      while(a[j]>=temp && j>i)
         j--;

      if(j>i)
         a[i++]=a[j];

      while(a[i]<=temp && j>i)
          i++;

      if(j>i)
          a[j--]=a[i];
   }

   a[i]=temp;
   quickSort(a,left,i-1);/*递归左边*/
   quickSort(a,i+1,right);/*递归右边*/
}

void qsort(int a[], int n) {
       quickSort(a, 0, n-1);

}

5:排序时间复杂度空间复杂度比较

排序算法 平均时间 最坏情况 辅助存储 是否是稳定算法
简单排序 O(n^2) O(n^2) O(1)
快速排序 O(nlogn) O(n^2) O(logn)
堆排序 O(nlogn) O(nlogn) 0(1)
归并排序 O(nlogn) O(nlogn) O(n)
基数排序 O(d(n+rd)) O(d(n+rd)) O(rd)

6:查找(折半查找/HASH查找)

查找:将表中记录的关键字与查找关键字比较,如果两者相等,则查找成功,返回查找位置。

包含:链表查找,数组查找,二叉树查找,hash

6.1: 折半查找

折半查找:又叫二分查找,首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。

image.png

注意:折半查找必须满足两个条件:一,元素必须是连续存储;二,元素必须有序。时间复杂度:O(logn)

//折半查找算法:
//a为存放数据的有序表,n为数据元素个数,k为要查找的元素
int bin_search(int a[], int n, int k) {
     int low, high, mid, i;

     low = 0;
     high = n-1;

     while (low <= high) {
         mid = (low + high)/2;

         if (a[mid] < k)
              low = mid + 1;
         else if (a[mid] > k)
              high = mid - 1;
         else {
              return mid;
         }
     }

     return -1;
}

//递归版本:

 

int iter_biSearch(int data[], const int x, int beg, int last) {  
    int mid = -1;  
    mid = (beg + last) / 2;  

    if (x == data[mid]) {  
        return mid;  
    } else if (x < data[mid]) {  
        return iter_biSearch(data, x, beg, mid - 1);  
    } else if (x > data[mid]) {  
        return iter_biSearch(data, x, mid + 1, last);  
    }  

    return -1;  
} 

int bin_search2(int a[], int n, int k) {
    return  iter_biSearch(a,k,0,n-1);
}

6.2:Hash表

Hash表用于存放key-value数据。比如一个学生的成绩,那么学生的学号可以当做key,成绩当做value,存放与hash表中。

image.png

Hash查找必须提供一个Hash函数,用于通过Key来计算数据存放在hash表中的位置。一般hash函数可以设计为key%N,其中N为hash表中元素的个数(一般为质数)。

假如HASH表的大小为N,那么Hash函数为:Hash(key)=key%N

当对于不同的两个key,计算出来的hash值可能相同,在相同的时候,就叫做hash冲突。解决hash冲突的方法不止一种,比如通过链式法解决,即将所有含有相同hash值的数据存放在同一个链表中,而将链表的头结点存放在HASH表中。

所谓hash查找,就是通过对应的key,按照hash函数,计算出数据在hash表中的位置。Hash查找的复杂度为O(1),所以具有较高的查找效率。

6.3:二叉搜索树查找

typedef struct _node {
     int data;
     struct _node *left;
     struct _node *right;
} node, btree;

btree *search(btree *b, int x) {
     if (b == NULL) {
         return NULL;
     } else {
         if (b->data == x) {
              return b;
         } else if (x < b->data) {
              return (search(b->left));
         } else {
              return (search(b->right));
         }
     }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容