这几天学习了基于比较的排序算法,有插入排序、归并排序、堆排序和快速排序。下面自己用C++代码实现了一下:
1、插入排序,插入排序的思想比较简单,就是像我们打扑克牌把手中的牌整理好一样,假设手中的牌是已经排好的,只需要找找到适当的位置把下一张牌插入到已有的牌中就好了。
void InsertionSort(int *A,int beg,int end){
int key;
for(int i=beg+1;i<=end;i++){//对所有的牌进行遍历,假设手中只有一张牌。
key=A[i];//用key来表示要插入的牌
int j=i-1;
while(j>=0 && A[j]>key){//找到正确的位置:如果手中的牌比要插入的大就往后移,直到正确的位置
A[j+1]=A[j];
j--;
}
A[j+1]=key;//把要插入的牌放到正确的位置
}
}
2、归并排序,归并排序使用了分治的方法,即把问题分为若干个子问题再分别解决每个子问题。主要思想是这样的,如果a和b是两个已经排好序的数组,那么可以比较a和b中的元素大小,把他们合并成一个排好序的数组。所以就把整个要排序的数组进行划分,直到每个字数组中只有一个元素(一个元素肯定是排好序的)。然后开始合并,直到最后合并成原长度的数组。这里用了一个Merge作为合并函数。
void Merge(int *A,int beg,int mid,int end){//合并函数 int n1=mid-beg+1,n2=end-mid; int i1=beg,i2=mid+1; vector<int> v;//用了一个vector来做中间数组,这里我纯粹是为了练习,完全可以用一个数组 while(i1<=mid && i2<=end){ if(A[i1]<A[i2]){ v.push_back(A[i1++]); } else v.push_back(A[i2++]); } while(i1<=mid){ v.push_back(A[i1++]); } while(i2<=end){ v.push_back(A[i2++]); } int n=beg; auto it=v.begin(); while(it!=v.end()){//将合并好的数组拷入到原数组中 A[n++]=*it; it++; } }
void MergeSort(int *A,int beg,int end){//进行归并 if(beg<end){//如果数组元素不为1,就进行划分 int mid=beg+(end-beg)/2; MergeSort(A,beg,mid); MergeSort(A,mid+1,end); Merge(A,beg,mid,end); } }
3、堆排序,堆排序是使用了最大堆这种数据结构。最大堆就是堆顶的元素是堆中最大的。堆排序的主要思想就是用数组来构造最大堆。这样每次把堆顶元素和数组中最后一个元素调换位置,直到堆中只有一个元素,这样数组的元素就从大到小了。这个算法写了一个堆的类,用成员函数进行排序。
class Maxheap{
private:
int size;//存放堆得大小
int length;//存放数组的长度
vector<int> data;//存放堆中元素
public:
Maxheap(){size=0;length=0;}
void Add(int value){
data.push_back(value);
size++;
length++;
}
void MaxHeapify(int i);
void BuildMaxheap();
void HeapSort();
int Get(int i){if(i<length)return data[i];else cout<<"overflow";}
int Getlen(){return length;}
};
void Maxheap::MaxHeapify(int i){//这个函数用来不断的维护堆的性质,因为建堆的时候需要用到,而且调换了堆顶元素和堆中最后一个元素后会破坏堆的性质。
int l=2*i+1,r=2*i+2;//找到左孩子和又孩子的序号
int largest=i,temp;
if(l<size && data[l]>data[i]){
largest=l;
}
if(r<size && data[r]>data[largest]){
largest=r;
}//将根、左孩子、右孩子中最大的作为根
if(largest!=i){//如果左子树和右子树的根变化,可能破坏了子树的性质,所以再次对子树进行维护。
temp=data[largest];
data[largest]=data[i];
data[i]=temp;
MaxHeapify(largest);
}
}
void Maxheap::BuildMaxheap(){//建堆
size=length;
for(int i=length/2-1;i>=0;i--){//因为对树来说,后n/2节点为叶节点,所以不需要维护,只维护前n/2
MaxHeapify(i);
}
}
void Maxheap::HeapSort(){//排序
BuildMaxheap();//先建堆
int temp;
while(size>1){//然后不断的把堆顶元素移到堆尾,并且维护堆的性质
temp=data[size-1];
data[size-1]=data[0];
data[0]=temp;
size--;
MaxHeapify(0);
}
}
4、快速排序,这个排序相对比较难懂,也是用了分治的策略。把目标数组的最后一个元素作为主元素,然后对前n-1个进行遍历,试数组被分为起点到i,i到j,j到末尾3段,第一段的数都小于主元素,第二段的数都大于主元素,第三段没有排,然后知道第三段的元素都被移除到第一段和第二段。再把主元素和到i+1的位置(大于主元素的位置)的元素调换,这样就被分成了两段,前一段都小于主元素,后一段都大于主元素。再对这两段递归的执行相同算法。
int Partition(int *A,int beg,int end){//用于划分
int key=A[end];
int i=beg-1,j=beg;
int temp;
while(j<end){
if(A[j]<key){
temp=A[++i];
A[i]=A[j];
A[j++]=temp;
}
else
j++;
}
temp=A[++i];
A[i]=A[end];
A[end]=temp;
return i;//返回后i之前的元素都小于A[i],i之后的都大于A[i]
}
void QuickSort(int *A,int beg,int end){
if(beg<end){
int p=Partition(A,beg,end);
QuickSort(A,beg,p-1);
QuickSort(A,p+1,end);
}
}
除了插入排序的时间复杂度是N平方外,其他都是nlgn,对于快速排序来说这个时间复杂度是平均的,而不是最坏的,当然如果随机的来选取主元素的话,是可以打到更好的效果,这里需要的数学证明比较多,就不说了哈。nlgn也是基于比较排序的算法的时间复杂度下限,也就是最优的。其中归并排序不是原址的,也就是会占用和问题大小相关的存储空间。其他三个算法都是原址的。