排序算法比较
#include <iostream>
#include <vector>
#include <time.h>
using namespace std;
void printVector(vector<int> v)
{
for(int i = 0; i < v.size(); i++)
cout << v[i] << " ";
cout << endl;
}
void swapElements(vector<int> &v, int i, int j)
{
int tmp;
if(i >= 0 && i < v.size() && j >= 0 && j < v.size())
{
tmp = v[j];
v[j] = v[i];
v[i] = tmp;
}
}
//选择排序
/*在要排序的一组数中,选出最小的一个数与第一个位置的数交换;
然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环
到倒数第二个数和最后一个数比较为止。
选择排序是不稳定的。算法复杂度O(n2)*/
void selectSort(vector<int> &v)
{
for(int i = 0; i < v.size()-1; i++)
{
for(int j = i+1; j < v.size();j++)
{
if(v[j] < v[i])
swapElements(v,i,j);
}
}
}
//直接插入排序
//算法思想简单描述:
//在要排序的一组数中,假设前面(n-1) [n>=2] 个数已经是排
//好顺序的,现在要把第n个数插到前面的有序数中,使得这n个数
//也是排好顺序的。如此反复循环,直到全部排好顺序。
//直接插入排序是稳定的。算法时间复杂度O(n2)
void insertSort(vector<int> &v)
{
for(int i = 1; i < v.size(); i++)
{
int j = i-1;
int tmp = v[i];
while(j >= 0 && v[j] > tmp)
{
v[j+1] = v[j];
j--;
}
v[j+1] = tmp;
}
}
//冒泡排序
//在要排序的一组数中,对当前还未排好序的范围内的全部数,自上
//而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较
//小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要
//求相反时,就将它们互换。
//冒泡排序是稳定的。算法时间复杂度O(n2)
void bubbleSort(vector<int> &v)
{
int sz = v.size()-1;
while(sz > 1)
{
for(int i = 0; i < sz; i++)
{
int j = i+1;
if(v[i] > v[j])
swapElements(v,i,j);
}
sz--;
}
}
//希尔排序
//算法思想简单描述:
//
//在直接插入排序算法中,每次插入一个数,使有序序列只增加1个节点,
//并且对插入下一个数没有提供任何帮助。如果比较相隔较远距离(称为
//增量)的数,使得数移动时能跨过多个元素,则进行一次比较就可能消除
//多个元素交换。D.L.shell于1959年在以他名字命名的排序算法中实现
//了这一思想。算法先将要排序的一组数按某个增量d分成若干组,每组中
//记录的下标相差d.对每组中全部元素进行排序,然后再用一个较小的增量
//对它进行,在每组中再进行排序。当增量减到1时,整个要排序的数被分成
//一组,排序完成。
//
//下面的函数是一个希尔排序算法的一个实现,初次取序列的一半为增量,
//以后每次减半,直到增量为1。
//
//希尔排序是不稳定的。
void insertSortIncr(vector<int> &v, int incr)
{
for(int i = incr; i < v.size(); i+= incr)
{
int j = i - incr;
int tmp = v[i];
while(j >= 0 && v[j] > tmp)
{
v[j+incr] = v[j];
j -= incr;
}
v[j+incr] = tmp;
}
}
void shellSort(vector<int> &v)
{
int h = v.size()/2;
while(h)
{
insertSortIncr(v,h);
h = h/2;
}
}
//快速排序
//快速排序是对冒泡排序的一种本质改进。它的基本思想是通过一趟
//扫描后,使得排序序列的长度能大幅度地减少。在冒泡排序中,一次
//扫描只能确保最大数值的数移到正确位置,而待排序序列的长度只
//减少1。快速排序通过一趟扫描,就能确保某个数(以它为基准点吧)
//的左边各数都比它小,右边各数都比它大。然后又用同样的方法处理
//它左右两边的数,直到基准点的左右只有一个元素为止。
//快速排序是不稳定的。最理想情况算法时间复杂度O(nlog2n),最坏O(n2)
void quickSort(vector<int> &v,int l, int r)
{
if(l < r)
{
int pivot = v[l];
int i = l;
int j = r;
while(i < j)
{
while(i < j && v[j] > pivot)
j--;
if(i < j)
{
v[i] = v[j];
i++;
}
while(i < j && v[i] < pivot)
i++;
if(i < j)
{
v[j] = v[i];
j--;
}
v[i] = pivot;
quickSort(v,l,i-1);
quickSort(v,i+1,r);
}
}
}
//堆排序
//堆排序是一种树形选择排序,是对直接选择排序的有效改进。
//堆的定义如下:具有n个元素的序列(h1,h2,...,hn),当且仅当
//满足(hi>=h2i,hi>=2i+1)或(hi <=h2i,hi <=2i+1)(i=1,2,...,n/2)
//时称之为堆。在这里只讨论满足前者条件的堆。
//
//由堆的定义可以看出,堆顶元素(即第一个元素)必为最大项。完全二叉树可以
//很直观地表示堆的结构。堆顶为根,其它为左子树、右子树。
//初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储顺序,
//使之成为一个堆,这时堆的根节点的数最大。然后将根节点与堆的最后一个节点
//交换。然后对前面(n-1)个数重新调整使之成为堆。依此类推,直到只有两个节点
//的堆,并对它们作交换,最后得到有n个节点的有序序列。
//
//从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素
//交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数
//实现排序的函数。
//
//堆排序是不稳定的。算法时间复杂度O(nlog2n)。
void maxHeapify(vector<int> &v, int i)
{
int l = 2*i;
int r = 2*i+1;
printVector(v);
int pos;
if(l < v.size() && v[l] < v[i])
{
pos = l;
}
else
pos = i;
if(r < v.size() && v[r] < v[pos])
pos = r;
if(pos != i)
{
swapElements(v,i,pos);
maxHeapify(v,pos);
}
}
void heapSort(vector<int> &v)
{
int h = v.size()/2-1;
while(h>=0)
{
maxHeapify(v,h);
h--;
}
}
int main()
{
vector<int> v;
for(int i = 0; i < 10; i++)
v.push_back(rand()%10);
cout << "Before Sorting:*****************************************" << endl;
printVector(v);
//selectSort(v);
//insertSort(v);
//bubbleSort(v);
//shellSort(v);
quickSort(v,0,v.size()-1);
//srand(time(NULL));
//heapSort(v);
cout << "After Sorting:*****************************************" << endl;
printVector(v);
return 0;
}
#include <vector>
#include <time.h>
using namespace std;
void printVector(vector<int> v)
{
for(int i = 0; i < v.size(); i++)
cout << v[i] << " ";
cout << endl;
}
void swapElements(vector<int> &v, int i, int j)
{
int tmp;
if(i >= 0 && i < v.size() && j >= 0 && j < v.size())
{
tmp = v[j];
v[j] = v[i];
v[i] = tmp;
}
}
//选择排序
/*在要排序的一组数中,选出最小的一个数与第一个位置的数交换;
然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环
到倒数第二个数和最后一个数比较为止。
选择排序是不稳定的。算法复杂度O(n2)*/
void selectSort(vector<int> &v)
{
for(int i = 0; i < v.size()-1; i++)
{
for(int j = i+1; j < v.size();j++)
{
if(v[j] < v[i])
swapElements(v,i,j);
}
}
}
//直接插入排序
//算法思想简单描述:
//在要排序的一组数中,假设前面(n-1) [n>=2] 个数已经是排
//好顺序的,现在要把第n个数插到前面的有序数中,使得这n个数
//也是排好顺序的。如此反复循环,直到全部排好顺序。
//直接插入排序是稳定的。算法时间复杂度O(n2)
void insertSort(vector<int> &v)
{
for(int i = 1; i < v.size(); i++)
{
int j = i-1;
int tmp = v[i];
while(j >= 0 && v[j] > tmp)
{
v[j+1] = v[j];
j--;
}
v[j+1] = tmp;
}
}
//冒泡排序
//在要排序的一组数中,对当前还未排好序的范围内的全部数,自上
//而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较
//小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要
//求相反时,就将它们互换。
//冒泡排序是稳定的。算法时间复杂度O(n2)
void bubbleSort(vector<int> &v)
{
int sz = v.size()-1;
while(sz > 1)
{
for(int i = 0; i < sz; i++)
{
int j = i+1;
if(v[i] > v[j])
swapElements(v,i,j);
}
sz--;
}
}
//希尔排序
//算法思想简单描述:
//
//在直接插入排序算法中,每次插入一个数,使有序序列只增加1个节点,
//并且对插入下一个数没有提供任何帮助。如果比较相隔较远距离(称为
//增量)的数,使得数移动时能跨过多个元素,则进行一次比较就可能消除
//多个元素交换。D.L.shell于1959年在以他名字命名的排序算法中实现
//了这一思想。算法先将要排序的一组数按某个增量d分成若干组,每组中
//记录的下标相差d.对每组中全部元素进行排序,然后再用一个较小的增量
//对它进行,在每组中再进行排序。当增量减到1时,整个要排序的数被分成
//一组,排序完成。
//
//下面的函数是一个希尔排序算法的一个实现,初次取序列的一半为增量,
//以后每次减半,直到增量为1。
//
//希尔排序是不稳定的。
void insertSortIncr(vector<int> &v, int incr)
{
for(int i = incr; i < v.size(); i+= incr)
{
int j = i - incr;
int tmp = v[i];
while(j >= 0 && v[j] > tmp)
{
v[j+incr] = v[j];
j -= incr;
}
v[j+incr] = tmp;
}
}
void shellSort(vector<int> &v)
{
int h = v.size()/2;
while(h)
{
insertSortIncr(v,h);
h = h/2;
}
}
//快速排序
//快速排序是对冒泡排序的一种本质改进。它的基本思想是通过一趟
//扫描后,使得排序序列的长度能大幅度地减少。在冒泡排序中,一次
//扫描只能确保最大数值的数移到正确位置,而待排序序列的长度只
//减少1。快速排序通过一趟扫描,就能确保某个数(以它为基准点吧)
//的左边各数都比它小,右边各数都比它大。然后又用同样的方法处理
//它左右两边的数,直到基准点的左右只有一个元素为止。
//快速排序是不稳定的。最理想情况算法时间复杂度O(nlog2n),最坏O(n2)
void quickSort(vector<int> &v,int l, int r)
{
if(l < r)
{
int pivot = v[l];
int i = l;
int j = r;
while(i < j)
{
while(i < j && v[j] > pivot)
j--;
if(i < j)
{
v[i] = v[j];
i++;
}
while(i < j && v[i] < pivot)
i++;
if(i < j)
{
v[j] = v[i];
j--;
}
v[i] = pivot;
quickSort(v,l,i-1);
quickSort(v,i+1,r);
}
}
}
//堆排序
//堆排序是一种树形选择排序,是对直接选择排序的有效改进。
//堆的定义如下:具有n个元素的序列(h1,h2,...,hn),当且仅当
//满足(hi>=h2i,hi>=2i+1)或(hi <=h2i,hi <=2i+1)(i=1,2,...,n/2)
//时称之为堆。在这里只讨论满足前者条件的堆。
//
//由堆的定义可以看出,堆顶元素(即第一个元素)必为最大项。完全二叉树可以
//很直观地表示堆的结构。堆顶为根,其它为左子树、右子树。
//初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储顺序,
//使之成为一个堆,这时堆的根节点的数最大。然后将根节点与堆的最后一个节点
//交换。然后对前面(n-1)个数重新调整使之成为堆。依此类推,直到只有两个节点
//的堆,并对它们作交换,最后得到有n个节点的有序序列。
//
//从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素
//交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数
//实现排序的函数。
//
//堆排序是不稳定的。算法时间复杂度O(nlog2n)。
void maxHeapify(vector<int> &v, int i)
{
int l = 2*i;
int r = 2*i+1;
printVector(v);
int pos;
if(l < v.size() && v[l] < v[i])
{
pos = l;
}
else
pos = i;
if(r < v.size() && v[r] < v[pos])
pos = r;
if(pos != i)
{
swapElements(v,i,pos);
maxHeapify(v,pos);
}
}
void heapSort(vector<int> &v)
{
int h = v.size()/2-1;
while(h>=0)
{
maxHeapify(v,h);
h--;
}
}
int main()
{
vector<int> v;
for(int i = 0; i < 10; i++)
v.push_back(rand()%10);
cout << "Before Sorting:*****************************************" << endl;
printVector(v);
//selectSort(v);
//insertSort(v);
//bubbleSort(v);
//shellSort(v);
quickSort(v,0,v.size()-1);
//srand(time(NULL));
//heapSort(v);
cout << "After Sorting:*****************************************" << endl;
printVector(v);
return 0;
}
算法 |
时间复杂度 |
空间复杂度 |
稳定性 |
归并 |
O(nlog2n) |
O(n) |
稳定 |
堆 |
O(nlgn) |
O(1) |
不稳定 |
选择 |
O(n2) |
O(1) |
不稳定 |
快排 |
O(nlgn) |
O(lgn) |
不稳定 |
冒泡 |
O(n2) |
O(1) |
稳定 |
希尔 |
O(1) |
不稳定 |
|
插入 |
O(n2) |
O(1) |
稳定 |