一,冒泡排序
void bublle_sort(int *a,int n) //n为数组a的元素个数
{
int i,j,temp;
for(j=0;j<n-1;++j)
for(i=0;i<n-1-j;++i)
if(a[i] > a[i+1]) //数组元素大小按升序排列
{
temp = a[i];
a[i] = a[i+1];
a[i+1] = temp;
}
}
二,选择排序
void selection_sort(int *a, int n)
{
int i, j, k, t;
for(i = 0; i < n - 1; ++i)
{
k = i;
//查找最小值
for(j = i + 1; j < n; ++j)
if(a[k] > a[j])
k = j;
//交换
if(k != i)
{
t = a[k];
a[k] = a[i];
a[i] = t;
}
}
}
三,直接插入排序
void insert_sort(int *a, int n)
{
int i,j,key;
for (j=1; j<n; ++j)
{
key=a[j];
i=j-1;
while (i>=0 && a[i]>key)
{
a[i+1]=a[i];
--i;
}
a[i+1]=key;
}
}
四,希尔排序
void shell_sort(int* a,int n)
{
int d = n/2; //设置希尔排序的增量
int i,j,key;
while(d>=1)
{
for(i=d; i<n; ++i)
{
key=a[i];
j=i-d;
while(j>=0 && a[j]>key)
{
a[j+d]=a[j];
j=j-d;
}
a[j+d] = key;
}
d= d/2; //缩小增量
}
}
五,归并排序
//将有二个有序数列a[first...mid]和a[mid...last]合并。
void merge(int a[], int first, int mid, int last, int temp[])
{
int i = first, j = mid + 1;
int m = mid, n = last;
int k = 0;
while (i <= m && j <= n)
{
if (a[i] <= a[j])
temp[k++] = a[i++];
else
temp[k++] = a[j++];
}
while (i <= m)
temp[k++] = a[i++];
while (j <= n)
temp[k++] = a[j++];
for (i = 0; i < k; i++)
a[first + i] = temp[i];
}
void mergesort(int a[], int first, int last, int temp[])
{
if (first < last)
{
int mid = (first + last) / 2;
mergesort(a, first, mid, temp); //左边有序
mergesort(a, mid + 1, last, temp); //右边有序
merge(a, first, mid, last, temp); //再将二个有序数列合并
}
}
bool MergeSort(int a[], int n)
{
int *p = new int[n];
if (p == NULL)
return false;
mergesort(a, 0, n - 1, p);
delete[] p;
return true;
}
六,快速排序
//p表示起点first, r表示终点last
int partition(int a[],int p,int r){
int x = a[r]; //通常,拿最后一个值,作为预期的中间值
int mid = p; //记录“较小的一段数据”的最大下标。通常这个值在p和r的中间,故起名mid
for (int j = p ; j < r ; j++){
if (a[j] < x){
int temp = a[mid];
a[mid] = a[j];
a[j] = temp;
mid++;
}
}
int temp = a[r];
a[r] = a[mid];
a[mid] = temp;
return mid;
}
void QuickSort(int a[],int p,int r)
{
if (p<r)
{
int q=partition(a,p,r);
QuickSort(a,p,q-1);
QuickSort(a,q+1,r);
}
}
说明:快速排序的随机化版本,就是有个随机值i,交换a[i] 与 a[r]的值,然后再执行partition函数
快速排序迭代版本:
void qsort_iter(int a[], int n)
{
if(n <= 1)
return ;
stack<int> s;
s.push(0); s.push(n-1);
while (!s.empty())
{
int r=s.top(); s.pop();
int p=s.top(); s.pop();
if(p>=r)
continue;
//partition
int x = a[r];
int mid = p;
for (int j = p ; j < r ; j++)
{
if (a[j] < x)
{
if (mid!=j)
std::swap(a[mid],a[j]);
mid++;
}
}
if (mid!=r)
std::swap(a[mid],a[r]);
//end partition
s.push(p); s.push(mid-1);
s.push(mid+1); s.push(r);
}
}
或者:
void qsort_iter(int a[], int n)
{
if(n <= 1)
return ;
stack<int> s;
s.push(0); s.push(n-1);
while (!s.empty())
{
int r=s.top(); s.pop();
int p=s.top(); s.pop();
if (p<r)
{
int mid=partition(a,p,r);
s.push(p); s.push(mid-1);
s.push(mid+1); s.push(r);
}
}
}
//快速排序的随机化版本
int rand_partition(int a[],int p,int r)
{
srand(time(NULL));
int i=p+rand()%(r-p+1); //产生一个p-r之间的一个随机数
int temp = a[r];
a[r] = a[i];
a[i] = temp;
return partition(a,p,r);
}
void rand_QuickSort(int a[],int p,int r)
{
if (p<r)
{
int q=rand_partition(a,p,r);
rand_QuickSort(a,p,q-1);
rand_QuickSort(a,q+1,r);
}
}
七,堆排序 (最大堆)
#define LEFT(x) ( 2*(x)+1 )
#define RIGHT(x) ( 2*(x)+2 )
#define PARENT(x) (((x)-1)/2)
//调整最大堆函数
void MaxHeapify(int *a, int n, int i)
{
int left,right,largst;
left=LEFT(i);
right=RIGHT(i);
if (left<n && a[left]>a[i])
largst=left;
else
largst=i;
if (right<n && a[right]>a[largst])
largst=right;
if ( largst != i )
{
int tmp=a[i];
a[i]=a[largst];
a[largst]=tmp;
MaxHeapify(a,n,largst);
}
}
//建堆
void Bulid_MaxHeap(int *a, int n)
{
for (int i=n/2-1 ; i>=0; --i)
MaxHeapify(a,n,i);
}
//排序
void HeapSort(int *a, int n)
{
Bulid_MaxHeap(a,n);
for (int i=n-1; i>0; --i)
{
int tmp=a[i];
a[i]=a[0];
a[0]=tmp;
MaxHeapify(a,i,0);
}
}
八,计数排序 (只针对自然数进行排序)
//a表示待排序数组,b表示排序后的数组
void CountSort(int *a ,int *b, int n, int k)//n表示数组个数,k表示数组元素最大值
{
int i;
int* c=(int*)malloc(sizeof(int)*k);
memset(c,0,sizeof(int)*k);
for ( i = 0; i < n; ++i)
++c[a[i]];
for ( i = 1; i < k; ++i) c[i] += c[i-1];
for (int i = n-1; i >= 0; --i) b[--c[a[i]]] = a[i];
free(c);
}
九,基数排序(LSD)
伪代码:
RADIX-SORT (A,d)
for i=1 to d
do use a stable sort to sort array A on digit i
其中,A表示数组,每个元素都有d位数字(最大元素有d位,不足的高位补0),其中1在最低位,d 在最高位
下面的代码的stable sort采用了计数排序,因为对于整型,每位数的范围是0-9
//基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital)
//LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。
int maxbit(int data[],int n) //辅助函数,求数据的最大位数
{
int d = 1; //保存最大的位数
int p =10;
for(int i = 0;i < n; ++i)
{
while(data[i] >= p)
{
p *= 10;
++d;
}
}
return d;
}
void radixsort(int data[],int n) //基数排序
{
int d = maxbit(data,n);
int * tmp = new int[n];
int * count = new int[10]; //计数器
int i,j,k;
int radix = 1;
for(i = 1; i<= d;i++) //进行d次排序
{
for(j = 0;j < 10;j++)
count[j] = 0; //每次分配前清空计数器
for(j = 0;j < n; j++)
{
k = (data[j]/radix)%10; //统计每个桶中的记录数
count[k]++;
}
for(j = 1;j < 10;j++)
count[j] = count[j-1] + count[j]; //将tmp中的位置依次分配给每个桶
for(j = n-1;j >= 0;j--) //将所有桶中记录依次收集到tmp中
{
k = (data[j]/radix)%10;
tmp[--count[k]] = data[j];
//tmp[count[k]-1] = data[j];
//count[k]--;
}
for(j = 0;j < n;j++) //将临时数组的内容复制到data中
data[j] = tmp[j];
radix = radix*10;
}
delete [] tmp;
delete [] count;
}
十,桶排序
伪代码:
function bucket-sort(array, n) is
buckets ← new array of n empty lists
for i = 0 to (length(array)-1) do
insert array[i] into buckets[msbits(array[i], k)]
for i = 0 to n - 1 do
next-sort(buckets[i])
return the concatenation of buckets[0], ..., buckets[n-1]
对于N个待排数据,M个桶,平均每个桶[N/M]个数据的桶排序平均时间复杂度为:
O(N)+O(M*(N/M)*log(N/M))=O(N+N*(logN-logM))=O(N+N*logN-N*logM)
当N=M时,即极限情况下每个桶只有一个数据时。桶排序的最好效率能够达到O(N)。
下面的代码就是N=M极限情况,有点类似哈希表的功能
void bucket_sort(int* a,int n, int maxNumber)
{
int len=maxNumber + 1;
int* sorted = new int[len];
for (int i = 0; i < n; i++)
{
sorted[a[i]] = a[i];
}
int k=0;
for (int i = 0; i <=maxNumber; i++)
{
if (sorted[i] > 0)
a[k++]=sorted[i];
}
delete[] sorted;
}
//链式存储
#include<iostream.h>
#include<malloc.h>
typedef struct node{
int key;
struct node * next;
}KeyNode;
void inc_sort(int keys[],int size,int bucket_size)
{
KeyNode **bucket_table=(KeyNode **)malloc(bucket_size*sizeof(KeyNode *));
for(int i=0;i<bucket_size;i++)
{
bucket_table[i]=(KeyNode *)malloc(sizeof(KeyNode));
bucket_table[i]->key=0; //记录当前桶中的数据量
bucket_table[i]->next=NULL;
}
for(int j=0;j<size;j++)
{
KeyNode *node=(KeyNode *)malloc(sizeof(KeyNode));
node->key=keys[j];
node->next=NULL;
//映射函数计算桶号
int index=keys[j]/10;
//初始化P成为桶中数据链表的头指针
KeyNode *p=bucket_table[index];
//该桶中还没有数据
if(p->key==0)
{
bucket_table[index]->next=node;
(bucket_table[index]->key)++;
}
else
{
//链表结构的插入排序
while(p->next!=NULL&&p->next->key<=node->key)
p=p->next;
node->next=p->next;
p->next=node;
(bucket_table[index]->key)++;
}
}
//打印结果
for(int b=0;b<bucket_size;b++)
for(KeyNode *k=bucket_table[b]->next; k!=NULL; k=k->next)
cout<<k->key<<" ";
cout<<endl;
}
void main(){
int raw[]={49,38,65,97,76,13,27,49};
int size=sizeof(raw)/sizeof(int);
inc_sort(raw,size,10);
}
比较与总结
比较排序包括:快速排序;堆排序;归并排序;插入排序;希尔排序;选择排序;冒泡排序
比较排序有很多性能上的根本限制。在最差情况下,任何一种比较排序至少需要O(nlogn)比较操作,这是比较操作所获的信息有限所导致的,或者说是全序集的模糊代数结构所导致的。从这个意义上讲,归并排序,堆排序在他们必须比较的次数上是渐进最优的,虽然这忽略了其他的操作。前面提到的三种非比较排序算法通过非比较操作能在O(n)完成,这使他们能够回避O(nlogn)这个下界(假设元素是定值)。
不过,比较排序在控制比较函数方面有显著优势,因此比较排序能对各种数据类型进行排序,并且可以很好地控制一个序列如何被排序。例如,如果倒置比较函数的输出结果可以让排序结果倒置。或者可以构建一个按字典顺序排序的比较函数,这样排序的结果就是按字典顺序的。
比较排序可以更好地适应复杂顺序,例如浮点数。并且,一旦比较函数完成,任何比较算法都可以不经修改地使用;而非比较排序对数据类型的要求更严格。
这种灵活性和上述比较排序在现代计算机的执行效率一起导致了比较排序被更多地应用在了大多数实际工作中。
1 ,快速排序(QuickSort)
快速排序是一个就地排序,分而治之,大规模递归的算法。从本质上来说,它是归并排序的就地版本。快速排序可以由下面四步组成。
(1) 如果不多于1个数据,直接返回。
(2) 一般选择序列最左边的值作为支点数据。
(3) 将序列分成2部分,一部分都大于支点数据,另外一部分都小于支点数据。
(4) 对两边利用递归排序数列。
快速排序比大部分排序算法都要快。尽管我们可以在某些特殊的情况下写出比快速排序快的算法,但是就通常情况而言,没有比它更快的了。快速排序是递归的,对于内存非常有限的机器来说,它不是一个好的选择。、
2, 归并排序(MergeSort)
归并排序先分解要排序的序列,从1分成2,2分成4,依次分解,当分解到只有1个一组的时候,就可以排序这些分组,然后依次合并回原来的序列中,这样就可以排序所有数据。合并排序比堆排序稍微快一点,但是需要比堆排序多一倍的内存空间,因为它需要一个额外的数组。
3 ,堆排序(HeapSort)
堆排序适合于数据量非常大的场合(百万数据)。
堆排序不需要大量的递归或者多维的暂存数组。这对于数据量非常巨大的序列是合适的。比如超过数百万条记录,因为快速排序,归并排序都使用递归来设计算法,在数据量非常大的时候,可能会发生堆栈溢出错误。堆排序会将所有的数据建成一个堆,最大的数据在堆顶,然后将堆顶数据和序列的最后一个数据交换。接下来再次重建堆,交换数据,依次下去,就可以排序所有的数据。
4, Shell排序(ShellSort)
Shell排序通过将数据分成不同的组,先对每一组进行排序,然后再对所有的元素进行一次插入排序,以减少数据交换和移动的次数。平均效率是O(nlogn)。其中分组的合理性会对算法产生重要的影响。现在多用D.E.Knuth的分组方法。
Shell排序比冒泡排序快5倍,比插入排序大致快2倍。Shell排序比起QuickSort,MergeSort,HeapSort慢很多。但是它相对比较简单,它适合于数据量在5000以下并且速度并不是特别重要的场合。它对于数据量较小的数列重复排序是非常好的。
5 ,插入排序(InsertSort)
插入排序通过把序列中的值插入一个已经排序好的序列中,直到该序列的结束。插入排序是对冒泡排序的改进。它比冒泡排序快2倍。一般不用在数据大于1000的场合下使用插入排序,或者重复排序超过200数据项的序列。
6 ,冒泡排序(BubbleSort)
冒泡排序是最慢的排序算法。在实际运用中它是效率最低的算法。它通过一趟又一趟地比较数组中的每一个元素,使较大的数据下沉,较小的数据上升。它是O(n^2)的算法。
7, 交换排序(ExchangeSort)和选择排序(SelectSort)
这两种排序方法都是交换方法的排序算法,效率都是 O(n2)。在实际应用中处于和冒泡排序基本相同的地位。它们只是排序算法发展的初级阶段,在实际中使用较少。
8 ,基数排序(RadixSort),计数排序(Counting sort),桶排序(Bucket sort)
计数排序,基数排序和桶排序是非比较的排序的算法,他们是线性时间的复杂度,即O(n);但是它只能用于整数的排序,如果我们要把同样的办法运用到浮点数上,我们必须了解浮点数的存储格式,并通过特殊的方式将浮点数映射到整数上,然后再映射回去,这是非常麻烦的事情,因此,它的使用同样也不多。而且,最重要的是,这样算法也需要较多的存储空间。
排序稳定性
稳定排序:
* 泡沫排序(bubble sort) — O(n²)
* 插入排序 (insertion sort)— O(n²)
* 桶排序 (bucket sort)— O(n); 需要 O(k) 额外空间
* 计数排序 (counting sort) — O(n+k); 需要 O(n+k) 额外空间
* 合并排序 (merge sort)— O(n log n); 需要 O(n) 额外空间
* 二叉排序树排序 (Binary tree sort) — O(n log n)期望时间; O(n²)最坏时间; 需要 O(n)额外空间
* 基数排序 (radix sort)— O(n·k); 需要 O(n) 额外空间
.
不稳定排序
* 选择排序 (selection sort)— O(n²)
* 希尔排序 (shell sort)— O(n log n) 如果使用最佳的现在版本
* 堆排序 (heapsort)— O(n log n)
* 快速排序 (quicksort)— O(n log n) 期望时间, O(n2) 最坏情况; 对于大的、乱数串行一般相信是最快的已知排序
排序法 | 平均时间 | 最差情形 | 稳定度 | 额外空间 | 备注 |
冒泡 | O(n^2) | O(n^2) | 稳定 | O(1) | n小时较好 |
交换 | O(n^2) | O(n^2) | 不稳定 | O(1) | n小时较好 |
选择 | O(n^2) | O(n^2) | 不稳定 | O(1) | n小时较好 |
插入 | O(n^2) | O(n^2) | 稳定 | O(1) | 大部分已排序时较好 |
基数 | O(logrd) | O(logrd) | 稳定 | O(n) | d是关键字项数(0-9),r是基数(个十百) |
Shell | O(nlogn) | O(ns) 1<s<2 | 不稳定 | O(1) | s是所选分组 |
快速 | O(nlogn) | O(n^2) | 不稳定 | O(nlogn) | n大时较好 |
归并 | O(nlogn) | O(nlogn) | 稳定 | O(1) | n大时较好 |
堆 | O(nlogn) | O(nlogn) | 不稳定 | O(1) | n大时较好 |