1. 程式人生 > 其它 >C語言qsort排序教程

C語言qsort排序教程

qsort函式用法簡介

前言

在這裡先感謝zjh和oyhd兩位學長。沒有他們兩位的悉心指導,我想我肯定不能在短時間學會並熟練使用qsort函式,並在程設期末時用上它。

以下開始正文

先上一個題鋪墊一下

大家看完題後應該不難理解,其實就是一個雙關鍵字排序,先排成績再排姓名的字典序(字典序其實就是根據每個字元的ASCII碼值對字串進行比較

下面直接給例題程式碼了(先把這個程式碼看明白那qsort大部分內容就差不多了

 #include<stdio.h>
 #include<stdlib.h>//qsort在這個函式庫裡面
 #include<string.h>
 struct in{//結構體變數,儲存每個人的成績和名字
     int score;//成績
     char name[15];//姓名
 }a[1005];
 int cmp(const void *p,const void *q);{//qsort的比較函式,用於確定比較規則
 int main(){
     int n;
     scanf("%d",&n);
     for(int i=1;i<=n;++i){//讀入
         scanf("%s %d",a[i].name,&a[i].score);//這個是結構體變數的引用,下面會仔細講
    }
     qsort(a+1,n,sizeof(a[1]),cmp);//直接qsort排序就行了,是不是很方便hh
     for(int i=1;i<=n;++i){//輸出
         printf("%s %d\n",a[i].name,a[i].score);
    }
     /*主函式裡面其實相當簡單對吧,讀入以後直接排序,排完序輸出就可以了*/
 }
 int cmp(const void *p,const void *q){
     struct in c=*(struct in*)p;
     struct in d=*(struct in*)q;//定義結構體c和結構體d來儲存p和q中的值,就是套路,記下來用多了就明白了
     if(c.score!=d.score){//先比較成績,再比較姓名字典序
         return c.score>d.score?-1:1;//
    } else return strcmp(c.name,d.name);//否則直接返回strcmp的值就行了,這就是按字典序排序
 }

各位看了這道題以後應該大概就明白qsort有多好用了(用冒泡寫起來應該不容易),下面我大概介紹這個函式的一些內容(隨便看看就好,有些也不重要

qsort函式簡介

首先qsort其實是Quicksort也就是快速排序的縮寫(qsort函式在C語言中是在<stdlib.h>這個函式庫裡,要使用時記得#include<stdlib.h>

其實大家在學習排序的過程中,肯定會先學過氣泡排序和選擇排序

那為什麼還要學qsort函式呢?

下面簡單說一下,以氣泡排序為例

大家在用氣泡排序的時候肯定感覺挺麻煩的(每次都要套一下模板,而且如果涉及多關鍵字排序(比如又要根據字典序排序,又要根據學號排序,又要根據分數排序等等),冒泡寫起來會很複雜

同時氣泡排序雖然是一個穩定的排序,但是它的平均時間複雜度其實達到了O(n^2)(而且你在寫的時候還要用仔細思考一下判斷條件,實在是有些麻煩;

於是接下來我就再來介紹一下快速排序和qsort;

首先快速排序雖然是一種不穩定的排序,但是它快啊!,快排的時間複雜度平均能達到O(nlogn)(雖然其他的一些歸併排序什麼的也能達到這個排序速度,但是函式庫裡沒有現成的給你用qsort直接拿來用還是很香的,而且qsort對於多個變數排序時十分方便(只要你學會結構體的相關知識),而且在一些什麼字典序排序時只需要return strcmp()就可以了,能大大的減小你在排序時的思維難度

嗯,下面正式介紹qsort函式,直接上程式碼塊了

 首先qsort函式裡面需要傳遞4個引數
 大概是這樣void qsort(void *base,size_t num,size_t width,int (*cmp)(const void *p,const void *q)){
 }

1.先解釋一下這個,首先第一個void *base,表示一個不確定型別的指標,所以你可以傳任意型別的指標進去,比如double型別的陣列,int型別的陣列,或者結構體指標進去(這裡說一下,比如int a[50],你可以直接傳a進去,表示從a[0]開始排序,如果改成a+1就是從a[1]開始排序(我一般喜歡傳a+1進去,看個人習慣了) 2.第二個size_t num表示你需要排序的元素的個數,也就是從你傳入的位置往後選取num個元素,然後對這num個元素進行排序 3.size_t width 這個表示一個元素的大小,就拿上面那個陣列的例子,就傳a[0]進去就行了 4.int (*cmp)(const void *p,const void *q),這是一個函式指標,這個表示傳入一個int型別的比較函式(一般就返回1或-1,就根據這個返回值來確定比較的大小關係),然後cmp函式接收的引數是兩個不確定型別的指標(主要是防止中間把指標型別更改了,所以const 不可少,就當套路記下來吧),然後這兩個指標之後需要強制型別轉換(轉換的型別其實就是你排序元素的型別),下面我會給例子的

然後這個qsort的實現原理大家感興趣的話可以自己上網去搜索一下,我這裡具體就不細說了(上面的東西看不懂也沒關係,下面看例子就明白了)

qsort的排序規則是根據cmp函式的返回值來確定的

預設的規則是升序排序(從小到大)排序,cmp函式返回正數表示a大於b,返回負數表示a小於b,

也就是比如你比較c和d兩個元素的大小,如果返回1就表示c大於d也就是會把c往後排

同時很關鍵的一點,不少寫法中會在cmp函式中 使用 return (*(int *)c- *(int *)d)來表示返回值,

當然int型別不會有問題,但是如果是double型別的話,由於浮點數的誤差會導致排序時出現一些錯誤

所以建議大家統一用下面的三目運算子進行cmp函式的書寫 (比如 return c>d?1:-1)//這個就是升序排序,如果需要降序排序就把1和-1換個位置就好了

這是一個具體的簡單例子,比如我要對a陣列的前10項從小到大排序

 #include<stdio.h>
 #include<stdlib.h>
 int a[50];
 int cmp(const void *p,const void *q){//const void *p的寫法背下來就行了
     int c=*(int *)p;//這裡表示用c接受p指標所存的值
     int d=*(int *)q;//這裡表示用d接受q指標所存的值
     
     if(c!=d){//這裡用if判斷一下,也就是c和d不相等時才返回1或-1;
         return c>d?1:-1;//用三目運算子來進行值得返回
    }
     
     return 0;//返回0就表示兩個數相等
 }
 int main (){
     for(int i=1;i<=10;++i){
         scanf("%d",&a[i]);
    }
     qsort(a+1,10,sizeof(a[0]),cmp);
     for(int i=1;i<=10;++i) printf("%d ",a[i]);
     return 0;
 }

大家可以試著自己先跑一下這個,熟悉一下qsort的寫法套路;

結構體簡介

接下來我大概介紹一下結構體,主要便於講解多關鍵字排序;

 struct in{
     int arr[10];
     int num;
     char s[100];
 };
 這個就是對一個結構體的定義了
 上面的 in 表示對結構體的標記,可以類似為命名(主要是拿來區分不同的結構體)
 然後大括號內表示的就是,一個結構體變數所包含的元素:
 以這個為例,一個結構體變數裡面有一個int型別長度為10的陣列,還有一個int型別變數num,還有一個長度為100的字串s

下面我給個結構體變數的定義

 struct in{
     int arr[10];
     int num;
     char s[100];
 }a[100];//寫在末尾表示開了100個這樣的結構體,每個結構體裡面都包含有一個int型別陣列,字串s,int型別變數num

還有一種

 struct in{
     int arr[10];
     int num;
     char s[100];
 };
 struct in a[100];

大家有興趣還可以去了解一下typedef。

大概給個例子

 typedef struct in{
     int arr[10];
     int num;
     char s[100];
 }std;
 std a[100];

其實就是把struct in這一串縮寫成std,能少打幾個字母。

好了,下面再講一下結構體的引用(主要以結構體變數的方式給出

比如我想使用第一個結構體元素中的num變數

 int n;
 n=a[1].num//這裡結構體變數引用其中元素就直接用“.”就可以引用,結構體指標才用->

如果想引用其中的字串

 char str[100];
 strcmp(str,a[1].s);

cmp函式模板

這裡我再介紹一下qsort函式一個關鍵的部分

cmp函式!(其實命名成什麼都可以,傳到qsort裡面就行了

比如我現在隨便給個例子,就比如:

給一個50個人的班級的期末考試(只有語數英三科)從大到小排名,先排總分,總分一樣排語文,依次排數學和英語,最後排一下姓名的字典序

 //先開一個結構體
 struct in{
  int Chinese;
  int Math;
  int English;
  int sum;//總分
  char name[15];
 }a[52];
 //接下來寫cmp函式
 int cmp(const void *p,const void *q){
     struct in c=*(struct in*)p;
     struct in d=*(struct in*)q;
     if(c.sum!=d.sum){//排總分
         return c.sum>d.sum?-1:1;//c的總分高就排返回-1,排到前面去
    } else if(c.Chinese!=d.Chinese) return c.Chinese>d.Chinese?-1:1;//排語文
     else if(c.Math!=d.Math) return c.Math>d.Math?-1:1;//排數學
     else if(c.English!=d.English) return c.English>d.English?-1:1;//排英語
     else return strcmp(c.name,d.name); //排姓名
 }

嗯,這個就差不多是多關鍵字排序了,其實就是結構體多添幾個變數,cmp函式裡面多寫幾個if else 而已hh;

最後需要注意的是,cmp中的const void *p,const void *q最好不要改,就當作約定俗成;

還有關於裡面c和d兩個變數的定義

就看你qsort傳進去的是什麼型別;

  • int型別的陣列就寫(double等等型別類比就好了)

     int c=*(int *)p;
     int d=*(int *)q;//這裡其實就是把void 型別的p,q指標強制型別轉換成對應型別,同時用“*”取出裡面的值;
  • 結構體型別就寫

     struct in c=*(struct in*)p;
     struct in d=*(struct in*)q;//這裡將就用in來命名結構體了,實際操作的時候具體看你們給結構體是怎樣命名的;

     

例題及程式碼

最後再貼一道例題吧

 

 例題程式碼:

 #include<stdio.h>
 #include<stdio.h>
 #include<math.h>
 #include<string.h>
 #include<stdlib.h>
 int read();
 struct In
 {
     int num;
     int a[12];
 }s[1005];
 int cmp(const void *p1,const void *p2);
 int m;
 int main()
 {
   int n;
   n=read();
   m=read();
   for(int i=1;i<=n;i++)
  {
       s[i].num=i;
       for(int j=1;j<=m;j++)
      {
           s[i].a[j]=read();
      }
  }
   qsort(s+1,n,sizeof(struct In),cmp);
   for(int i=1;i<=n;i++)
  {
       printf("%d ",s[i].num);
  }
 return 0;
 }
 int read(){
 int x=0,f=1,c=getchar();
 while(c<'0'||c>'9')(c=='-')?(f=-1):0,c=getchar();
 while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
 return x*f;
 }
 int cmp(const void *p1,const void *p2)
 {
     struct In c=*(struct In*)p1;
     struct In d=*(struct In*)p2;
     int x;
     for(x=1;x<=m;x++)
    {
         if(c.a[x]==d.a[x]) continue;
         else