快速排序 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.24的PercDown( 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就可以把平均速度算出来了。