数据结构--排序(下) 快排、桶排、基数排序、各种排序比较

快速排序 O(nlogn)

快排和归并排序都是采用分治的思想,快排的方法是先从一个序列中选一个主元出来,然后把序列根据主元分成左右两部分,左边比主元小,又边比主元大, 分出来的两部分在分别递归用同样的方法处理。
第一步:选主元
采取取 头 中 尾 的中位数作为主元,最后把主元放到倒数第二的位置,也就是n-1的位置。

ElementType Median3(ElementType A[] , int left , int right )
{
	int center = (right+left)/ 2 ;
	if (A[left] > A[center])
		Swap(A[left],A[center]);
	if (A[left] > A[right])
		Swap(A[left],A[right]);
	if (A[center] > A[right])
		Swap(A[center] , A[right]);
	Swap(A[center] , A[right-1]);
	return A[right-1];
 }  

第二步:子集划分 (关键步骤)
由于第一个元素一定比主元小,最后一个元素一定比主元大,所以只需要讲left+1 — right-2 的元素进行分类就好啦,比主元小的放左边,比主元大 的放右边。
具体实现方法:
假设选取的主元是6,设i ,j两个指针
在这里插入图片描述
当A[i] <6时,说明位置是对的,i往右边移动,当遇到a[i]>=6时,i停止移动,向左移动j,寻找一个A[j] <6的数与之交换。
在这里插入图片描述
上面的图可以看到,8是大于6的,因此i保持不动,移动j,当j移动到2的位置发现2<6,也停止移动,这时候i ,j 位置上的两个元素交换位置。

在这里插入图片描述
然后继续上面的步骤,先移动i再移动j。
直到i<j时停止移动。
在这里插入图片描述
最后把i位置和主元位置的6互换
在这里插入图片描述
这时候就可看到主元左边的元素都比主元小,右边的元素都比主元大!!接下来就继续把划分的两个部分进行同样的方法处理。


 		int mid = Median3(A,left, right);
 		int i = left ,j = right-1 ; 
 		while (true)
 		{
 			while (A[--left] <mid) ; //第一次从left-1开始判断 当遇到比mid大的数停止移动
			while(A[--right] > mid); // 第一次从right-2 开始判断,当遇到比mid小的数停止移动 
			if(i<j) Swap(A[i],A[j]); // 当i<j时停止 
			else break ;  
		}
		Swap(A[i],A[right-1]); //主元位置在right-1 与i 位置的元素互换
		Quicksort(A,left,i-1);
		Quicksort(A,i+1,Right); 

第三步:分类
由于快排是利用递归进行的,需要一直调用栈空间,如果数据较小的话效率反而会更低,所以当数据量小于一定数值时直接进行插入排序,反而会提高效率,我网上查了一些博客,最后个人选择10最为分解点,当数据小于10时,直接用插入排序!

ElementType Median3(ElementType A[] , int left , int right )
{
	int center = (right+left)/ 2 ;
	if (A[left] > A[center])
		Swap(A[left],A[center]);
	if (A[left] > A[right])
		Swap(A[left],A[right]);
	if (A[center] > A[right])
		Swap(A[center] , A[right]);
	Swap(A[center] , A[right-1]);
	return A[right-1];
 }  
 
 void Quicksort(ElementType A[] , int left , int right)
 {
 	if (10 <=right-left)
 	{
 		int mid = Median3(A,left, right);
 		int i = left ,j = right-1 ; 
 		while (true) 
 		{
 			while (A[++i] <mid) ; //第一次从left-1开始判断 当遇到比mid大的数停止移动
			while(A[--j] > mid); // 第一次从right-2 开始判断,当遇到比mid小的数停止移动 
			if(i<j) Swap(A[i],A[j]); // 当i<j时停止 
			else break ;  
		}
		Swap(A[i],A[right-1]); //主元位置在right-1 与i 位置的元素互换
		Quicksort(A,left,i-1);
		Quicksort(A,i+1,right);  
	}
	else 
	{
		Insertion_Sort(A+left,right-left +1);
	}
 }
 
 void Quick_Sort(ElementType A[], int n )
 {
 	Quicksort(A, 0,n-1);
 }

表排序

表排序是指对一些结构进行排序时,由于结构本身数据较大,排序时移动效率低,所以需要建一个数组table,每个位置先保存对应结构下标序号,然后对这个数组排序时,利用下标位置去访问对应的结构中的数据,从而只需对table中的序号进行排序,最后按着序号依次访问对应的结构,就是有序的访问次序了。

在这里插入图片描述
这里的table就是保存待排序结构的下标,当对table进行排序时,只需要访问A[table[i]]中的内容进行比较然后对table中的序号进行修改即可。

在这里插入图片描述
排序完的结构如图, 3 5 2 1 7 0 4 6 位置对应结果中的字母是abcdefg,完成了排序的目的。实例结合下面的桶排序给出。

桶排序 O(m+n)

如果数据范围较小,可以用桶排序来实现几乎线性的排序。
例:比如将一个班的高数成绩进行排序,因为成绩只有【0,100】的范围,我们可以开一个大小为101的表a,存放学生的数据,比如有一个人考了50分,就在a[50]中放入这个人的信息,最后只需要从0-100的顺序对a进行访问及排序好的顺序了。
如果利用上面表排序的方法,我只需要在a中存放对应学生数据的下标即可。
输入:
5
小林 100
小红 60
小张 45
小李 60
小月 50

#include <iostream>
#include <vector>
#include <string>
using namespace std ;

struct student{
	string name ;
	int score ;
	student(string s , int n):name(s),score(n){}
};

int main ()
{
	int n ;
	cin >> n ;
	vector <student> v ;
	for (int i=0;i<n;i++)
	{
		string name ;
		int score ;
		cin >> name >> score ;
		v.push_back(student(name,score));
	}
	vector <int> a[101];//表
	for (int i=0;i<n;i++) 
	{
		a[v[i].score].push_back(i); //将scaore成绩的学生的下标放到表a[score] 中 
	}
	for (int i=0;i<=100 ;i++)
	{
		if (!a[i].empty())
		{
			for (int j=0;j<a[i].size();j++)
			{
				int item = a[i][j] ;
				cout << v[item].name<<" "<<v[item].score <<endl;
			}
		}
	 } 
	
 } 

在这里插入图片描述
这个是我自己编的数据,由于数据少,数据的范围较大,所以效率不是很高,下面我写一个对100000个数据排序的桶排序。算法复杂度是O(n+m), n是指数据量,m是数据范围,如果m足够下,这个算法可以接近线性的速度。(通过是输出rand随机生成的100000个数据的查看,发现数据范围在[0,32767]应此m就是32768)
在这里插入图片描述
void Bucket_Sort (ElementType A[] , int n)
{
int a[32768];
memset(a,0,sizeof (a));
for (int i=0;i<n;i++)
{
a[A[i]] +=1;
}
int flag = 0 ;
for (int i=0;i<32768;i++)
{
while (a[i]>0)
{
A[flag ++] = i ;
a[i] – ;
}
}
}
在这里插入图片描述
如果不要复制结果的话时间可以再快一倍。
100000个数据比快排快了10倍!!!!所以如果知道数据范围且待排数据量远远大于数据范围的话,选择桶排序无疑是最好的选择,但是会浪费一定的空间。

基数排序

如果上面的m>>n时, 桶排序是不合算的,这样就要用基数排序一样是线性的时间。但是基数排序还是基于桶排序来实现的。
基数排序有次位优先和主位优先两种,这里以次位优先为例:

假设要排序最大三位数的一串数字,那么就要分别对个位十位百位进行三次桶排序,我们通过一个示例来说明方法。

输入序列: 64 ,8,216,512,27,729,0,1,343,125
设这么一个桶来进行排序
在这里插入图片描述
第一次对个位进行排序:
在这里插入图片描述
第二次对十位排序:
在这里插入图片描述
第三次对百位排序:
在这里插入图片描述
通过这三次排序就可以发现最后一次排序后已经变成有序了。
这里我们用队列去实现五位数的基数排序。

void Cardinal_Sort(ElementType A[], int n)
{
	queue<int> a[5][10]; // 桶 
	for (int i=0;i<n;i++) //个位 
	{
		a[0][A[i]%10].push(A[i]);	
	} 
	int number = 10 ,item ,i  ;
	for ( i=1;i<5;i++) // 十位 百位 千位 万位 
	{
		for (int j=0;j<=9;j++)
		{
			while (!a[i-1][j].empty()) // 从前一位i-1获取数据 
			{
				item = a[i-1][j].front() ;  a[i-1][j].pop();
				a[i][item/number%10].push(item); 
			}
		} 
		number *=10 ;//进一位比较 
	}
	number = 0 ;
	for (int j=0;j<=9;j++) //复制排序结果到A
	{
		while (!a[i-1][j].empty())
		{
			item = a[i-1][j].front() ;  a[i-1][j].pop();
			A[number++] = item ; 
		} 
	} 
 } 

这是我自己写的桶排序,效率和mooc上给的代码查不多。
在这里插入图片描述
下面给出mooc的代码:

 #define MaxDigit 5
#define Radix 10
 
/* 桶元素结点 */
typedef struct Node *PtrToNode;
struct Node {
    int key;
    PtrToNode next;
};
 
/* 桶头结点 */
struct HeadNode {
    PtrToNode head, tail;
};
typedef struct HeadNode Bucket[Radix];
  
int GetDigit ( int X, int D )
{ /* 默认次位D=1, 主位D<=MaxDigit */
    int d, i;
     
    for (i=1; i<=D; i++) {
        d = X % Radix;
        X /= Radix;
    }
    return d;
}
 
void LSDRadixSort( ElementType A[], int N )
{ /* 基数排序 - 次位优先 */
     int D, Di, i;
     Bucket B;
     PtrToNode tmp, p, List = NULL; 
      
     for (i=0; i<Radix; i++) /* 初始化每个桶为空链表 */
         B[i].head = B[i].tail = NULL;
     for (i=0; i<N; i++) { /* 将原始序列逆序存入初始链表List */
         tmp = (PtrToNode)malloc(sizeof(struct Node));
         tmp->key = A[i];
         tmp->next = List;
         List = tmp;
     }
     /* 下面开始排序 */ 
     for (D=1; D<=MaxDigit; D++) { /* 对数据的每一位循环处理 */
         /* 下面是分配的过程 */
         p = List;
         while (p) {
             Di = GetDigit(p->key, D); /* 获得当前元素的当前位数字 */
             /* 从List中摘除 */
             tmp = p; p = p->next;
             /* 插入B[Di]号桶尾 */
             tmp->next = NULL;
             if (B[Di].head == NULL)
                 B[Di].head = B[Di].tail = tmp;
             else {
                 B[Di].tail->next = tmp;
                 B[Di].tail = tmp;
             }
         }
         /* 下面是收集的过程 */
         List = NULL; 
         for (Di=Radix-1; Di>=0; Di--) { /* 将每个桶的元素顺序收集入List */
             if (B[Di].head) { /* 如果桶不为空 */
                 /* 整桶插入List表头 */
                 B[Di].tail->next = List;
                 List = B[Di].head;
                 B[Di].head = B[Di].tail = NULL; /* 清空桶 */
             }
         }
     }
     /* 将List倒入A[]并释放空间 */
     for (i=0; i<N; i++) {
        tmp = List;
        List = List->next;
        A[i] = tmp->key;
        free(tmp);
     } 
}

因为没有使用现成的队列,所以代码量比较大,可以自己选择实现方式,重要的是掌握排序的方法!!!!

排序算法的比较

在这里插入图片描述
现在真正自己写排序其实很少啦,学习排序主要是学习排序的方法,算法效率提高的技巧,以及对之前数据结构的掌握。而且没有绝对好的排序算法,没种算法都有自己的局限性,要么需要额外,要么不稳定。

最后给出这上面排序的所有对比代码:

#include <iostream>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <queue>
#define ElementType int 
#define Max 100000
using namespace std ;

void Swap(int &a, int &b)
{
	int item = a ;
	a= b ;
	b =item ;
}

**************************冒泡*************************************  
void Bubble_Sort(ElementType A[] , int n)
{
	for (int i=n-1;i>=0;i--)
	{
		int flag = 0 ;
		for (int j=0;j<i;j++)
		{
			
			if (A[j]>A[j+1])
			{
				Swap(A[j],A[j+1]);
				flag = 1;  //记录是否发生交换 
			}
		}
		if (flag==0) break ; //未发生交换说明有序,结束排序 
	}
}

**************************插排*************************************  
void Insertion_Sort(ElementType A[] , int n)
{
	int p , i;
	for (i=1;i< n;i++)
	{
		int item = A[i];
		for ( p = i ; p>0 && A[p-1]>item ;p--)
			A[p] = A[p-1];
		A[p]  =  item ;
	}
}

//输出排序结果 
void print(ElementType A[] , int n)
{
	cout <<"排序结果:";
	for (int i=0;i<n;i++)
	cout << A[i]<<" ";
}

**************************希尔排序*************************************  
void Shell_sort(ElementType A[], int n)
{
	int Sedgewick[] = {0,1, 5 ,19, 41, 109, 209, 505, 929, 2161, 3905 ,8929, 16001, 36289, 64769, 146305, 260609 ,587521, 1045505, 2354689 ,4188161 }	;
	int si ,d,i,j,tmp;
	for (si = 0 ;Sedgewick[si]<=n;si++ );
	for ( d = Sedgewick[si] ; d>0 ; d=Sedgewick[--si] )
	{
		for ( i= d ; i<n ;i++)
		{
			tmp =  A[i];
			for ( j=i ;j>=d && A[j-d] >tmp;j-=d )
				A[j] = A[j-d];
			A[j] = tmp;
		}
	}
}

//**************************堆排*************************************  
void PercDown( ElementType A[], int p, int N )
{ /* 改编代码4.24PercDown( MaxHeap H, int p )    */
  /* 将N个元素的数组中以A[p]为根的子堆调整为最大堆 */
    int Parent, Child;
    ElementType X;
    X = A[p]; /* 取出根结点存放的值 */
    for( Parent=p; (Parent*2+1)<N; Parent=Child ) {
        Child = Parent * 2 + 1;
        if( (Child!=N-1) && (A[Child]<A[Child+1]) )
            Child++;  /* Child指向左右子结点的较大者 */
        if( X >= A[Child] ) break; /* 找到了合适位置 */
        else  /* 下滤X */
            A[Parent] = A[Child];
    }
    A[Parent] = X;
}
 
void HeapSort( ElementType A[], int N ) 
{ /* 堆排序 */
     int i;
       
     for ( i=N/2-1; i>=0; i-- )/* 建立最大堆 */
         PercDown( A, i, N );
      
     for ( i=N-1; i>0; i-- ) {
         /* 删除最大堆顶 */
         Swap( A[0],A[i] ); /* 见代码7.1 */
         PercDown( A, 0, i );
     }
}

//**************************归并*************************************  
void merge(ElementType A[] , ElementType B[],int L, int R ,int RightEnd)
{
	int LeftEnd = R-1 ;
	int flag  = L ;//初始位置
	int first = L ;
	int last = RightEnd; 
	while (L<=LeftEnd && R<= RightEnd)
	{
		if (A[L] > A[R])
			B[flag++] = A[R++];
		else 
			B[flag++] = A[L++];
	}
	while (L<=LeftEnd) B[flag ++] = A[L++];
	while (R<=RightEnd) B[flag++] = A[R++];
	
	for (int i = first ; i<=last ;i++)
		A[i] = B[i];
}

void Msort(ElementType A[], ElementType B[] ,int first ,int last)
{
	if (first < last)
	{
		int center = (first +last) / 2;
		Msort(A, B , first ,center);
		Msort(A,B,center+1 , last);
		merge(A,B,first,center+1 , last);
	}
	
}
void Merge_sort(ElementType A[], int n)
{
	ElementType *B ;
	B = (ElementType *)malloc(sizeof(ElementType)*n);
	Msort (A,B,0,n-1);
	free (B);
}

//**************************快排************************************* 
ElementType Median3(ElementType A[] , int left , int right )
{
	int center = (right+left)/ 2 ;
	if (A[left] > A[center])
		Swap(A[left],A[center]);
	if (A[left] > A[right])
		Swap(A[left],A[right]);
	if (A[center] > A[right])
		Swap(A[center] , A[right]);
	Swap(A[center] , A[right-1]);
	return A[right-1];
 }  
 
 void Quicksort(ElementType A[] , int left , int right)
 {
 	if (10 <=right-left)
 	{
 		int mid = Median3(A,left, right);
 		int i = left ,j = right-1 ; 
 		while (true) 
 		{
 			while (A[++i] <mid) ; //第一次从left-1开始判断 当遇到比mid大的数停止移动
			while(A[--j] > mid); // 第一次从right-2 开始判断,当遇到比mid小的数停止移动 
			if(i<j) Swap(A[i],A[j]); // 当i<j时停止 
			else break ;  
		}
		Swap(A[i],A[right-1]); //主元位置在right-1 与i 位置的元素互换
		Quicksort(A,left,i-1);
		Quicksort(A,i+1,right);  
	}
	else 
	{
		Insertion_Sort(A+left,right-left +1);
	}
 }
 
 void Quick_Sort(ElementType A[], int n )
 {
 	Quicksort(A, 0,n-1);
 }
 
 **************************桶排*************************************  
 void Bucket_Sort (ElementType A[] , int n)
 {
 	int a[32768];
	memset(a,0,sizeof (a));
	for (int i=0;i<n;i++)
	{
		a[A[i]] +=1;	
	}	
	int flag = 0 ;
	for (int i=0;i<32768;i++)
 	{
		while (a[i]>0)
		{
			A[flag ++] = i ;
			a[i] -- ;
		}
		
	}
 } 

//**************************队列实现基数排序*************************************
void Cardinal_Sort(ElementType A[], int n)
{
	queue<int> a[5][10]; // 桶 
	for (int i=0;i<n;i++) //个位 
	{
		a[0][A[i]%10].push(A[i]);	
	} 
	int number = 10 ,item ,i  ;
	for ( i=1;i<5;i++) // 十位 百位 千位 万位 
	{
		for (int j=0;j<=9;j++)
		{
			while (!a[i-1][j].empty()) // 从前一位i-1获取数据 
			{
				item = a[i-1][j].front() ;  a[i-1][j].pop();
				a[i][item/number%10].push(item); 
			}
		} 
		number *=10 ;//进一位比较 
	}
	number = 0 ;
	for (int j=0;j<=9;j++) //复制排序结果到A
	{
		while (!a[i-1][j].empty())
		{
			item = a[i-1][j].front() ;  a[i-1][j].pop();
			A[number++] = item ; 
		} 
	} 
 } 
**************************mooc给出的基数排序************************************* 
 #define MaxDigit 5
#define Radix 10
 
/* 桶元素结点 */
typedef struct Node *PtrToNode;
struct Node {
    int key;
    PtrToNode next;
};
 
/* 桶头结点 */
struct HeadNode {
    PtrToNode head, tail;
};
typedef struct HeadNode Bucket[Radix];
  
int GetDigit ( int X, int D )
{ /* 默认次位D=1, 主位D<=MaxDigit */
    int d, i;
     
    for (i=1; i<=D; i++) {
        d = X % Radix;
        X /= Radix;
    }
    return d;
}
 
void LSDRadixSort( ElementType A[], int N )
{ /* 基数排序 - 次位优先 */
     int D, Di, i;
     Bucket B;
     PtrToNode tmp, p, List = NULL; 
      
     for (i=0; i<Radix; i++) /* 初始化每个桶为空链表 */
         B[i].head = B[i].tail = NULL;
     for (i=0; i<N; i++) { /* 将原始序列逆序存入初始链表List */
         tmp = (PtrToNode)malloc(sizeof(struct Node));
         tmp->key = A[i];
         tmp->next = List;
         List = tmp;
     }
     /* 下面开始排序 */ 
     for (D=1; D<=MaxDigit; D++) { /* 对数据的每一位循环处理 */
         /* 下面是分配的过程 */
         p = List;
         while (p) {
             Di = GetDigit(p->key, D); /* 获得当前元素的当前位数字 */
             /* 从List中摘除 */
             tmp = p; p = p->next;
             /* 插入B[Di]号桶尾 */
             tmp->next = NULL;
             if (B[Di].head == NULL)
                 B[Di].head = B[Di].tail = tmp;
             else {
                 B[Di].tail->next = tmp;
                 B[Di].tail = tmp;
             }
         }
         /* 下面是收集的过程 */
         List = NULL; 
         for (Di=Radix-1; Di>=0; Di--) { /* 将每个桶的元素顺序收集入List */
             if (B[Di].head) { /* 如果桶不为空 */
                 /* 整桶插入List表头 */
                 B[Di].tail->next = List;
                 List = B[Di].head;
                 B[Di].head = B[Di].tail = NULL; /* 清空桶 */
             }
         }
     }
     /* 将List倒入A[]并释放空间 */
     for (i=0; i<N; i++) {
        tmp = List;
        List = List->next;
        A[i] = tmp->key;
        free(tmp);
     } 
}
 


int main ()
{
	
	clock_t start ,stop ;
	double T ;
	int A[Max] ,B[Max];
	for (int i=0 ; i< Max; i++)
		A[i] = rand();
		
	for (int i=0 ; i< Max; i++)
	B[i] = A[i];
	start = clock ();
	Bubble_Sort(B , Max);
	stop = clock();
	T = (double)(stop-start)/CLOCKS_PER_SEC;
	cout <<"冒泡排序用时:"<<T<<"s"<<endl;

	for (int i=0 ; i< Max; i++)
		B[i] = A[i];
	start = clock ();
	Insertion_Sort(B , Max);
	stop = clock();
	T = (double)(stop-start)/CLOCKS_PER_SEC;
	cout <<"插入排序用时:"<<T<<"s"<<endl;

	for (int i=0 ; i< Max; i++)
		B[i] = A[i];
	start = clock ();
	Shell_sort(B , Max);
	stop = clock();
	T = (double)(stop-start)/CLOCKS_PER_SEC;
	cout <<"希尔排序用时:"<<T<<"s"<<endl;
	
	for (int i=0 ; i< Max; i++)
		B[i] = A[i];
	start = clock ();
    HeapSort(B , Max);
	stop = clock();
	T = (double)(stop-start)/CLOCKS_PER_SEC;
	cout <<"堆排序用时:"<<T<<"s"<<endl;
	
	for (int i=0 ; i< Max; i++)
		B[i] = A[i];
	start = clock ();
	Merge_sort(B , Max);
	stop = clock();
	T = (double)(stop-start)/CLOCKS_PER_SEC;
	cout <<"归并排序用时:"<<T<<"s"<<endl;
	
	for (int i=0 ; i< Max; i++)
		B[i] = A[i];
	start = clock ();
	Quick_Sort(B , Max);
	stop = clock();
	T = (double)(stop-start)/CLOCKS_PER_SEC;
	cout <<"快速排序用时:"<<T<<"s"<<endl;
	
	for (int i=0 ; i< Max; i++)
		B[i] = A[i];
	start = clock ();
	Bucket_Sort(B , Max);
	stop = clock();
	T = (double)(stop-start)/CLOCKS_PER_SEC;
	cout <<"桶排序用时:"<<T<<"s"<<endl;
	
	for (int i=0 ; i< Max; i++)
		B[i] = A[i];
	start = clock ();
	Cardinal_Sort(B , Max);
	stop = clock();
	T = (double)(stop-start)/CLOCKS_PER_SEC;
	cout <<"队列实现基数排序用时:"<<T<<"s"<<endl;
	
	for (int i=0 ; i< Max; i++)
		B[i] = A[i];
	start = clock ();
	LSDRadixSort(B , Max);
	stop = clock();
	T = (double)(stop-start)/CLOCKS_PER_SEC;
	cout <<"mooc给出的基数排序用时:"<<T<<"s"<<endl;
 } 

在这里插入图片描述
桶排序显示0秒是因为太快了,计算不到时间,感兴趣的话可以让他重复运行10次,然后将最后的时间除10就可以把平均速度算出来了。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值