基数排序全面详解【数据结构】(桶排序、多关键字排序、链式基数排序)

基数排序

桶排序

首先来看看桶排序:
在这里插入图片描述
如果学生有1000个,我们只需要设置100个桶即可,然后按照桶从0-100来按顺序排序即可。

为什么桶排序的时间复杂度是O(M+N)?
因为分配的时间复杂度是M(也就是桶的数量),而收集起来的时间复杂度是N,因此最终时间复杂度是O(M+N)。

上面那种是理想的桶排序,因为最终成绩的范围是在小范围内。
但是如果成绩的范围很大:
在这里插入图片描述
对于一个数,例如523,排序的思想很简单,比它大的排它后面,比它小的排它前面。
那么一个数如果比523小,可以分为三种情况:

  1. 百位数比5小
  2. 百位数=5,十位数比2小
  3. 百位数=5,十位数=2,个位数比3小

那接下来来介绍一下基数排序,它是在桶排序的基础上进行优化的。基数排序就是对上面这三种情况都进行了排序。

基数排序又称多关键字排序,分为两种,MSD(Most Significant Digit)和LSD(Least Significant Digit)

其中我们看MSD,花色是更为重要的,其次看面值,而MSD就是给花色建4个桶,然后在桶内再用别的排序方法。
在这里插入图片描述
LSD就相反:
在这里插入图片描述

在大多数情况下LSD会比MSD的效率更高。

算法原理

那详细讲下多关键字排序的算法(这里采用LSD):

拿数字做例子(注意结合上面我说的523):
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

链式基数排序

在基数排序的实现方法中,通常用链式基数排序来实现:

使用链表效率更高,更方便些。当收集时,只需要修改指针,不需要移动元素

在这里插入图片描述
例如对三位数进行排序,我们可以这样:
在这里插入图片描述
(这里的队列其实不是stl的队列,而是类似桶的存在)

队列头尾指针也可以使用二级指针:
node **f;
node **r;

f=new node*[10]; r=new node*[10];

或者使用队列: queue q[10];

我们需要进行n趟分配和收集,n为数字的长度,每次分配和收集需要做这些事情:
在这里插入图片描述
在这里插入图片描述

看下面这个例子

在这里插入图片描述

而每次分配完之后需要将结果收集起来,从左至右(也就是从小到大、也就是升序)的收集起来。

然后接下来从最次位一直到第一位,对每种情况都分配桶。
在这里插入图片描述
在这里插入图片描述
最后收集的时候就可以得到排序的结果了,排序的次数是固定的,是你这个数的长度。

其实不难理解,对于上面的第一种情况,在第三趟分配和收集的时候会解决。
对于上面的第二种情况(百位相同十位不同),在第二趟分配和收集的时候会解决。
而上面的第三种情况,在第三趟分配和收集的时候会解决。

收集的作用

为什么分配完后要收集?
收集有什么作用?为什么可以体现这种作用?

在每次分配完桶的时候,要把结果从前往后串联下来,(可以想象一下按顺序整理扑克牌,A在最左边,K在最右边,然后从左往右把牌都收起来)。

例如523和521
收集的目的是为了针对上面所说的第三种情况,因为我们可以知道如果百位和十位相同,那么百位和十位分配桶的时候,是会分配到一个桶里面的。

那么我们希望最终结果中,百位和十位相同的情况下,个位小的,应该在数组的前面

因此这时候在最终的分配中,因为百位和十位相同,所以这两个在一个桶里,这时候我们就希望最终个位小的应该在队列更靠近前面的地方,这通过什么来实现呢?在链表结构中是表尾插入,那这就看,谁先插入,谁就在前面。
那这靠什么来实现呢?就靠最开始个位分配桶的时候,我们是从小的(也就是左边)开始往大的(也就是右边)串联起来,这就保证了,个位小的会出现在前面,也就是我们重新进行分配的时候,个位小的会更先进行分配。

性能分析

在这里插入图片描述
在这里插入图片描述

代码实现

要注意几个问题:
1、给定一组数据,到底要进行多少趟?
输入数据的同时,选择出最大的数,然后转换成字符串,求其长度
#include<bits/stdc++.h>
stringstream sstr;
sstr << max;
string s= sstr.str(); //或者string s; sstr>>s;
weishu=s.length();

或者写个循环,每次除以10(同时计数),直到商为0

2、如何求第i位的值?
for(i=1;i<=weishu;i++)
for(j=1;j<=len;j++)
int temp=(data[j] / (int)pow(10,i-1)) % 10;

class node{
public:   //结点结构
  int e; 
  node *next;
  node(){next=NULL;}
};

class Sort{
    int len;    //数据长度
    node *head; //头结点
    node *f[10];//队头指针
    node *r[10];//队尾指针
    int weishu; //处理位数
public: //各种方法  
	void init(){//将队头队尾指针设置成NULL
        for(int i=0;i<10;i++){
            f[i]=NULL;
            r[i]=NULL;
        }
    }
	 void RadixSort(){
	     int i; node* p;
	     for(i=1;i<=weishu;i++){
	        init();   //将队头队尾指针设置成NULL
	
	        p=head->next; //开始一趟分配的过程
	        while(p){
	           int w=( p->e /(int)pow(10,i-1))%10; //取得第i位的数据
	           if(f[w]==NULL)   f[w]=p; //若是该队列的第一个数据
	           else   r[w]->next=p; //否则,在队尾插入
	           r[w]=p; //队尾指针指向新插入的数据
	           p=p->next;
	        }
	        p=head;   //开始一趟收集的过程
	        for(int i=0;i<10;i++){  //共10个队列
	           if(f[i]){ //若第i个队列不空
	                p->next=f[i];
	                p=r[i];
	            }
	         }
	
	         p->next=NULL;  //收集结束,链表加上结束标志。
	     }
	}
}

设立队尾指针的原因

看图举例
在这里插入图片描述
如果不设立队尾指针,那么我们遍历十个队列的每一个队列的时候,比如遍历f[0],从f[0]的第一个元素到最后一个元素,此时遍历:008 063 083 184 589,会把不在f[0]的但是在f[0]最后一个元素后面链接的元素也遍历了!

因此需要设立队尾指针作为哨兵来查看是否遍历到了最后一个元素。

例题及完整代码

在这里插入图片描述
样例输入
2
10 278 109 63 930 589 184 505 269 8 83
6 57 0 93 19 18 99

样例输出
0:->930->^
1:NULL
2:NULL
3:->63->83->^
4:->184->^
5:->505->^
6:NULL
7:NULL
8:->278->8->^
9:->109->589->269->^
930 63 83 184 505 278 8 109 589 269
0:->505->8->109->^
1:NULL
2:NULL
3:->930->^
4:NULL
5:NULL
6:->63->269->^
7:->278->^
8:->83->184->589->^
9:NULL
505 8 109 930 63 269 278 83 184 589
0:->8->63->83->^
1:->109->184->^
2:->269->278->^
3:NULL
4:NULL
5:->505->589->^
6:NULL
7:NULL
8:NULL
9:->930->^
8 63 83 109 184 269 278 505 589 930

0:->0->^
1:NULL
2:NULL
3:->93->^
4:NULL
5:NULL
6:NULL
7:->57->^
8:->18->^
9:->19->99->^
0 93 57 18 19 99
0:->0->^
1:->18->19->^
2:NULL
3:NULL
4:NULL
5:->57->^
6:NULL
7:NULL
8:NULL
9:->93->99->^
0 18 19 57 93 99

答案代码:

#include<iostream>
#include<cmath>
using namespace std;
class node{
public:   //结点结构
    int e;
    node *next;
    node(){next=NULL;}
};

class Sort{
    int len;    //数据长度
    node *head; //头结点
    node *f[10];//队头指针
    node *r[10];//队尾指针
    int weishu; //处理位数
public: //各种方法
    Sort(){
        weishu=0;
        cin>>len;
        head=new node;
        int max=0;
        for(int i=0;i<len;i++){
            int num;
            cin>>num;
            if(num>max)max=num;
            node *p=new node;
            p->e=num;
            node *q=head;
            for(int j=0;j<i;j++)q=q->next;
            q->next=p;
        }
        while(max/10){
            max/=10;
            weishu++;
        }
        weishu++;
        //display();
    }
    void displayData(){
        node *p=head->next;
        while(p){
            cout<<p->e<<" ";
            p=p->next;
        }
        cout<<endl;
        //cout<<weishu<<endl;
    }

    void init(){//将队头队尾指针设置成NULL
        for(int i=0;i<10;i++){
            f[i]=NULL;
            r[i]=NULL;
        }
    }

    void RadixSort(){
        int i; node* p;
        for(i=1;i<=weishu;i++){
            init();   //将队头队尾指针设置成NULL

            p=head->next; //开始一趟分配的过程
            while(p){
                int w=( p->e /(int)pow(10,i-1))%10; //取得第i位的数据
                if(f[w]==NULL)   f[w]=p; //若是该队列的第一个数据
                else r[w]->next=p; //否则,在队尾插入
                r[w]=p; //队尾指针指向新插入的数据
                p=p->next;
            }
            p=head;   //开始一趟收集的过程
            for(int i=0;i<10;i++){  //共10个队列
                if(f[i]){ //若第i个队列不空
                    p->next=f[i];
                    p=r[i];
                }
            }

            p->next=NULL;  //收集结束,链表加上结束标志。
            displayQueue();
            displayData();
        }
        cout<<endl;
    }


    void displayQueue(){
        for(int i=0;i<10;i++){
            cout<<i<<":";
            if(f[i]==NULL)cout<<"NULL";
            else{
                node *p=f[i];
                while(p&&p!=r[i]){
                    cout<<"->"<<p->e;
                    p=p->next;
                }
                cout<<"->"<<r[i]->e<<"->^";
            }
            cout<<endl;
        }
    }
};
int main(){
    int t;
    cin>>t;
    while(t--){
        Sort test;
        test.RadixSort();
        //test.display();
    }
    return 0;
}

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页