您的位置:首页 > 编程语言 > C语言/C++

各种内排序算法的C++实现(不断更新中)

2010-09-28 14:18 441 查看
和很多计算机系的同学们一样,我在大学二年级时也学了《数据结构》这门课。当时我的老师是一个中科大的博士,现在已经是教授了。他在课上曾经这样评价这门课:《数据结构》几乎是所有计算机课程的基础课,如果把这门课学好了,其他的专业课就不成问题了。还有,IT公司的面试经常涉及到数据结构的相关知识,该课程的重要性由此可见。但是当时年少无知根本没好好学习,等到笔试,面试时才幡然悔悟。下面的内排序算法可算是数据结构中的重要内容,程序代码全部用C++实现,已在visual C++6.0上运行过了。

一.插入排序(insert sorting)

最差情况下,直接插入排序的最大时间代价为θ(n²),最小时间代价为θ(n),平均时间代价为θ(n²)。

#include <iostream>
using namespace std;

/*交换函数,作用是交换数组中的两个元素的位置*/
void swap(int array[],int i,int j)
{
int tmp=array[i];
array[i]=array[j];
array[j]=tmp;
}

/*插入排序*/
void InsertSort(int array[],int n)
{
for(int i=1;i<n;i++)
{
for(int j=i;j>0;j--)
{
if(array[j]>array[j-1])
swap(array,j,j-1);
else
break;
}
}
}

int main()
{
int array[5]={3,1,2,5,4};
InsertSort(array,5);
for(int i=0;i<5;i++)
cout<<array[i]<<"  ";
cout<<endl;
return 0;
}


二.冒泡排序(bubble sorting)

冒泡排序的最大时间代价,最小时间代价和平均时间代价均为θ(n²)。

#include <iostream>
using namespace std;

/*交换函数,作用是交换数组中的两个元素的位置*/
void swap(int array[],int i,int j)
{
int tmp=array[i];
array[i]=array[j];
array[j]=tmp;
}

/*冒泡排序*/
void BubbleSort(int array[],int n)
{
for(int i=0;i<n-1;i++)
{
for(int j=n-1;j>i;j--)
{
if(array[j]<array[j-1])
swap(array,j,j-1);
}
}
}

int main()
{
int array[5]={3,1,2,5,4};
BubbleSort(array,5);
for(int i=0;i<5;i++)
cout<<array[i]<<"  ";
cout<<endl;
return 0;
}


三.选择排序(selection sorting)

选择排序的最大时间代价,最小时间代价和平均时间代价均为θ(n²)。选择排序不依赖于原始数组的输入顺序。

#include <iostream>
using namespace std;

/*交换函数,作用是交换数组中的两个元素的位置*/
void swap(int array[],int i,int j)
{
int tmp=array[i];
array[i]=array[j];
array[j]=tmp;
}

/*选择排序*/
void SelectionSort(int array[],int n)
{
for(int i=0;i<n-1;i++)
{
int smallest=i;
for(int j=i+1;j<n;j++)
{
if(array[smallest]>array[j])
smallest=j;
}
swap(array,i,smallest);
}
}

int main()
{
int array[5]={3,1,2,5,4};
SelectionSort(array,5);
for(int i=0;i<5;i++)
cout<<array[i]<<"  ";
cout<<endl;
return 0;
}


四.shell sorting

增量为2的shell排序的时间代价可以达到θ(n的3/2次方),有的增量可以达到θ(n的7/6次方),很接近θ(n)。

#include <iostream>
using namespace std;

/*交换函数,作用是交换数组中的两个元素的位置*/
void swap(int array[],int i,int j)
{
int tmp=array[i];
array[i]=array[j];
array[j]=tmp;
}

/*希尔排序*/
void ShellSort(int array[],int n)
{
for(int delta=n/2;delta>0;delta/=2)
{
for(int i=0;i<delta;i++)
{
for(int j=i+delta;j<n;j+=delta)
{
for(int k=j;k>0;k-=delta)
{
if(array[k]<array[k-1])
swap(array,k,k-1);
}
}
}
}
}

int main()
{
int array[8]={6,8,7,3,1,2,5,4};
ShellSort(array,8);
for(int i=0;i<8;i++)
cout<<array[i]<<"  ";
cout<<endl;
return 0;
}


五.快速排序(quick sorting)

快速排序的最大时间代价为θ(n²),最小时间代价为θ(n*logn),平均时间代价为θ(n*logn)。注意:快速排序是一种不稳定的排序方式,其性能依赖于原始数组的有序程度,更进一步分析,就是依赖与轴值元素的选择。快排的比较次数远多于移动次数,所以主要考虑比较次数。
快排中,每一次比较可以确定一个轴值元素的位置。若p[m,q,n](q为轴值元素)。当然确定第一个轴值元素也是要比较(n-m-1)次。但第二个轴值元素,第三个轴值元素就要进行(q-m-1)和(n-q-1)次比较。如果q的值若为m或n,快速排序就退化成冒泡排序了,快排就没有什么优势了。

#include <iostream>
using namespace std;

/*交换函数,作用是交换数组中的两个元素的位置*/
void swap(int array[],int i,int j)
{
int tmp=array[i];
array[i]=array[j];
array[j]=tmp;
}

/*将轴值放到数组的适当的位置*/
int partition(int array[],int left,int right)
{
int mid=(left+right)/2;
int tmp=array[mid];
swap(array,mid,right);
int i=left;
int j=right;
while(1)
{
/*i指针向右移动,直到找到一个大于轴值的值*/
while(1)
{
/*如果i与j相遇则确定轴值位置,将其返回*/
if(i==j)
{
array[i]=tmp;
return i;
}
if(array[i]>tmp)
{
array[j]=array[i];
j--;
break;
}
i++;
}
/*j指针向左移动,直到找到一个小于轴值的值*/
while(1)
{
/*如果i与j相遇则确定轴值位置,将其返回*/
if(i==j)
{
array[j]=tmp;
return j;
}
if(array[j]<tmp)
{
array[i]=array[j];
i++;
break;
}
j--;
}
}
}

/*快速排序*/
void quickSort(int array[],int left,int right)
{
if(right<=left)
return;
int pivot=partition(array,left,right);
quickSort(array,left,pivot-1);
quickSort(array,pivot+1,right);
}

int main()
{
int array[8]={6,8,7,3,1,2,5,4};
quickSort(array,0,7);
for(int i=0;i<8;i++)
cout<<array[i]<<"  ";
cout<<endl;
return 0;
}


六.归并排序(merge sorting)

归并排序的最大时间代价,最小时间代价和平均时间代价均为θ(n*logn)。归并排序不依赖于原始数组的有序程度。

#include <iostream>
using namespace std;

/*归并过程--将两个有序数组合并成一个有序数组*/
void merge(int array[],int tempArray[],int left,int right,int middle)
{
int index1=left;
int index2=middle+1;
for(int i=left;(index1<=middle)&&(index2<=right);i++)
{
if(array[index1]<array[index2])
tempArray[i]=array[index1++];
else
tempArray[i]=array[index2++];
}
while(index1<=middle)
tempArray[i++]=array[index1++];
while(index2<=right)
tempArray[i++]=array[index2++];
for(i=left;i<=right;i++)
array[i]=tempArray[i];
}

/*两路归并排序--array[]为待排数组,tempArray[]为临时数组,left和right分别是数组两端*/
void mergeSort(int array[],int tempArray[],int left,int right)
{
if(left<right)
{
int middle=(left+right)/2;
mergeSort(array,tempArray,left,middle);
mergeSort(array,tempArray,middle+1,right);
merge(array,tempArray,left,right,middle);
}
}

int main()
{
int array[8]={6,8,7,3,1,2,5,4};
int tempArray[8];
mergeSort(array,tempArray,0,7);
for(int i=0;i<8;i++)
cout<<array[i]<<"  ";
cout<<endl;
return 0;
}


七.堆排序(heap sorting)

堆排序的最大时间代价,最小时间代价和平均时间代价均为θ(n*logn)。堆排序和归并排序一样,不依赖于原始数组的有序程度。

HeapSorting.cpp

#include <iostream>
#include "MaxHeap.h"
using namespace std;

/*最大堆排序函数*/
void heapSort(int array[],int n)
{
MaxHeap max_heap=MaxHeap(array,n);

/*删除堆的最大值(堆顶),即每次将最大值与数组的最后一个元素交换位置*/
for(int i=0;i<7;i++)
max_heap.removeMax();
}

int main()
{
int array[8]={4,3,7,1,2,8,5,6};
heapSort(array,8);
for(int i=0;i<8;i++)
cout<<array[i]<<"  ";
cout<<endl;
return 0;
}


MaxHeap.h

#include <iostream>
using namespace std;

/*最大堆定义*/
class MaxHeap
{
private:
int size; //最大堆的元素数目
int * array;  //最大堆数组的首地址指针
public:
MaxHeap(int array[],int n); //用已有数组初始化一个最大堆
void buildHeap();   //构建最大堆
void siftDown(int index);  //向下筛选法
void swap(int index1,int index2);  //交换位置为index1与index2的元素
void removeMax();  //删除堆顶的最大值--与数组最后一个元素交换位置并重新构建最大堆
int leftChild(int index);  //返回左孩子的位置
int rightChild(int index);  //返回右孩子的位置
};


MaxHeap.cpp

#include <iostream>
#include "MaxHeap.h"
using namespace std;

/*最大堆成员函数实现*/
MaxHeap::MaxHeap(int array[],int n)
{
this->array=array;
size=n;
buildHeap();
}

void MaxHeap::buildHeap()
{
for(int i=size/2-1;i>=0;i--)
siftDown(i);
}

void MaxHeap::siftDown(int index)
{
int max_index=leftChild(index);
while(max_index<size)
{
if(max_index<size-1&&array[rightChild(index)]>array[max_index])
max_index++;
if(array[index]>array[max_index])
break;
swap(index,max_index);
index=max_index;
max_index=leftChild(index);
}
}

void MaxHeap::swap(int index1,int index2)
{
int temp=array[index1];
array[index1]=array[index2];
array[index2]=temp;
}

void MaxHeap::removeMax()
{
swap(0,size-1);
size--;
siftDown(0);
}

int MaxHeap::leftChild(int index)
{
return index*2+1;
}

int MaxHeap::rightChild(int index)
{
return index*2+2;
}


八.基数排序(radix sorting)

基数排序的时间复杂度为O (nlog(r)m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的比较性排序法。基数排序法是属于稳定性的排序。

#include <iostream>
using namespace std;

/*计算关键字位数的最大值*/
int KeySize(int array[],int size)
{
int key_size=1;

for(int i=0;i<size;i++)
{
int temp=1;
int n=10;
while(array[i]/n>0)
{
temp++;
n*=10;
}
key_size=(temp>key_size)?temp:key_size;
}
return key_size;
}

/*基数排序*/
void RadixSort(int array[],int size)
{
int bucket[10][10]={0};//定义基数桶
int order[10]={0};//保存每个基数桶之中的元素个数
int key_size=KeySize(array,size);//计算关键字位数的最大值

for(int n=1;key_size>0;n*=10,key_size--)
{
/*将待排序的元素按照关键值的大小依次放入基数桶之中*/
for(int i=0;i<size;i++)
{
int lsd=(array[i]/n)%10;
bucket[lsd][order[lsd]]=array[i];
order[lsd]++;
}

/*将基数桶中的元素重新串接起来*/
int k=0;
for(i=0;i<10;i++)
{
if(order[i]!=0)
{
for(int j=0;j<order[i];j++)
{
array[k]=bucket[i][j];
k++;
}
order[i]=0;
}
}
}
}

int main()
{
int array[10]={73,22,93,43,55,14,28,65,39,81};
int size=sizeof(array)/sizeof(int);
RadixSort(array,size);
for(int i=0;i<size;i++)
cout<<array[i]<<"  ";
cout<<endl;
return 0;
}


下面我们来讨论一下各种排序算法的稳定度,稳定排序算法会依照相等的关键(换言之就是值)维持纪录的相对次序。也就是一个排序算法是稳定的,就是当有两个有相等关键的纪录R和S,且在原本的串列中R出现在S之前,在排序过的串列中R也将会是在S之前。

  一般的方法:插入、交换、选择、合并等等。交换排序包含冒泡排序(bubble sort)和快速排序(quicksort)。选择排序包含shaker排序和堆排序(heapsort)。

  当相等的元素是无法分辨的,比如像是整数,稳定度并不是一个问题。然而,假设以下的数对将要以他们的第一个数字来排序。

  (4, 1) (3, 1) (3, 7) (5, 6)

  在这个状况下,有可能产生两种不同的结果,一个是依照相等的键值维持相对的次序,而另外一个则没有:

  (3, 1) (3, 7) (4, 1) (5, 6) (维持次序)

  (3, 7) (3, 1) (4, 1) (5, 6) (次序被改变)

  不稳定排序算法可能会在相等的键值中改变纪录的相对次序,但是稳定排序算法从来不会如此。不稳定排序算法可以被特别地时作为稳定。作这件事情的一个方式是人工扩充键值的比较,如此在其他方面相同键值的两个物件间之比较,就会被决定使用在原先资料次序中的条目,当作一个同分决赛。然而,要记住这种次序通常牵涉到额外的空间负担。

稳定的排序算法:

冒泡排序(bubble sort) — O(n2)

  鸡尾酒排序 (Cocktail sort, 双向的冒泡排序) — O(n2)

  插入排序 (insertion sort)— O(n2)

  桶排序 (bucket sort)— O(n); 需要 O(k) 额外 记忆体

  归并排序 (merge sort)— O(n log n); 需要 O(n) 额外记忆体

  原地归并排序 — O(n2)

  二叉树排序 (Binary tree sort) — O(n log n); 需要 O(n) 额外记忆体

  基数排序 (radix sort)— O(n·k); 需要 O(n) 额外记忆体

不稳定的排序算法:

选择排序 (selection sort)— O(n2)

  希尔排序 (shell sort)— O(n log n) 如果使用最佳的现在版本

  Comb sort — O(n log n)

  堆排序 (heapsort)— O(n log n)

  Smoothsort — O(n log n)

  快速排序 (quicksort)— O(n log n) 期望时间, O(n2) 最坏情况; 对於大的、乱数串列一般相信是最快的已知排序

一般来说:存在不相邻交换的排序算法是不稳定的,相邻交换的排序算法是稳定的;对于相邻交换的稳定排序算法,通过控制交换条件可以转换成不稳定排序算法;冒泡、插入、归并和基数排序是稳定的;选择、快速、希尔和堆排序是不稳定的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: