如有错误,请及时指出!谢谢!
几种排序算法时间复杂度和空间复杂度:
1.冒泡排序
如果是从小到大排序,从最后一个元素开始,依次与前一个元素比较,比前一个元素小则交换。这样小的元素就浮到上面了。第二轮再从最后一个元素开始。
C++实现如下:(优化版,引入了flag)
void bubbleSort(vector<int> &A)
{
bool flag=1;
for (int j = 0; j < A.size() && flag; j++)
{
flag=0;
for (int i = A.size() - 1; i > j; i--)
{
//如果一直没有数据交换,则说明已经排好了,flag=0,在下一个for不执行直接结束
//这样就不用再进行后面的for循环了
if (A[i] < A[i - 1])
{
swap(A[i], A[i - 1]);
flag=1;
}
}
}
时间复杂度:最好O(n),最坏O(n^2),平均O(n^2)
冒泡排序状态变化举个栗子:
待排:37462
第一轮:37426、37246、32746、23746
第二轮:23746、23476
第三轮:23467,排好了……
2.简单选择排序
第一轮:j=0,先设第一个元素下标为min,将第一个元素(下标为0)与第二个(下标为1)比较,将min设置为较小元素的下标;再讲min与第三个比较,将将min设置为较小元素的下标。一轮比较之后,获得第一个小元素的下标min,将j=0位置元素与min位置元素交换。
第二轮:从第二个元素开始,获得第二小的元素
……
C++实现如下:
void selectionSort(vector<int> &A)
{
for (int j = 0;j < A.size();j++)
{
int min = j;
for (int i = j + 1;i < A.size();i++)
{
if (A[i] < A[min])
min = i;
}
if(j!=min) swap(A[j],A[min]);
}
}
时间复杂度:O(n^2)
简单选择排序状态变化举个栗子:
序号:01234
待排:37462
第一轮:初始化最小元素的下标min=0,经过比较min=4,即第4个元素2,交换第0个元素和第4个元素:27463
第二轮:初始化最小元素的下标min=1,经过比较min=4,即第4个元素3,交换第1个元素和第4个元素:23467
第三轮:初始化最小元素的下标min=2,经过比较min=2,不改变……实际上已经排好了……
3.直接插入排序
C++实现如下:
void insertionSort(vector<int> &A)
{
for (int j = 1;j < A.size();j++)
{
int key = A[j];
int i=j-1;
while(i>=0&&key < A[i])
{
A[i + 1] = A[i]; //向后移一位
i--;
}
A[i + 1] = key;
}
}
最好O(n),最坏O(n^2),平均O(n^2)
将小的元素不断往前移动,把比它大的元素不断往后移动。
直接插入排序状态变化举个栗子:
序号:01234
待排:53462
第一轮:j=1,key=A[1]=3,i=0 , key=3<A[0]=5 , A[1]=5,i=-1 ,跳出while,A[0]=3→35462
第二轮:j=2 , key=A[2]=4 , i=1 , key=4<A[1]=5, A[2]=5,i=0,跳出while,A[1]=4→34562
………………
4.堆排序
一些基本概念和定义:
大顶堆:完全二叉树,每个结点的值都大于其左右孩子的值;即A[i]≥A[2i],A[i]≥A[2i+1]
小顶堆:完全二叉树,每个结点的值都小于其左右孩子的值;即A[i]≤A[2i],A[i]≤A[2i+1]
完全二叉树,如果用一个数组存储(下标从0开始)。若下标为i,则双亲下标是【((i-1)/2)向下取整】;同理,下标为i的结点,它的左右孩子下标为2i+1和2i+2。
步骤:
(1)构造大顶堆
(2)堆顶移走(其实是与末尾元素交换)
(3)重复(1)
堆排序主函数:
基本步骤:
(1)从最后一个非叶子节点开始,对每个非叶子结点进行堆排序
(2)将堆顶与最后一个元素交换
(3)除去最后一个元素,重新构建大顶堆
void HeapSort(vector<int>& A)
{
int heap_size = A.size() - 1;
for (int i = (heap_size-1) / 2;i >= 0;i--)
//因为所有叶子结点已经是合法的大顶堆了,所以对所有非叶子结点构造大顶堆
{
maxHeapify(A, heap_size,i); //从下标为i到heap_size元素构造大顶堆
}
while (heap_size >= 1)
{
swap(A[heap_size], A[0]); //交换堆顶与末尾元素
heap_size--;
maxHeapify(A, heap_size, 0);
}
}
下面是构造大顶堆函数maxHeapify()的实现(递归和非递归):
//递归
//从下标为i到heap_size元素构造大顶堆
void maxHeapify(vector<int>& A, int heap_size, int i)
{
int largest = i * 2 + 1;
if (largest > heap_size) return;
if (largest<heap_size&&A[largest + 1]>A[largest])
{
largest++; //选出较大的孩子用于下一步与父节点交换值
}
if (A[i]<A[largest])
{ //如果自己的孩子比自己大则交换两者的值
swap(A[i], A[largest]);
maxHeapify(A, heap_size, largest);
}
}
//非递归
void maxHeapify(vector<int>& A, int heap_size, int i)
{
int largest;
while (i * 2 < heap_size)
{
largest = i * 2 + 1;
if (largest<heap_size&&A[largest + 1]>A[largest])
{
largest++;
}
if (A[i]<A[largest])
{ //如果自己的孩子比自己大则交换两者的值
swap(A[i], A[largest]);
}
else break; //如果不比自己大则不交换
i = largest;
}
}
由于每次重新恢复堆的时间复杂度为O(logN),共N - 1次重新恢复堆操作,再加上前面建立堆时N / 2次向下调整,每次调整时间复杂度也为O(logN)。
5.两路归并排序
两两组合,排序;归并,排序……
//递归的方法
void MergeSort(vector<int>& A)
{
MSort(A,0,A.size()-1);
}
void MSort(vector<int>& A,int low,int high)
{
if(low<high)
{
int mid=(low+high)/2;
MSort(A,low,mid);
Msort(A,mid+1,high);
Merge(A,low,mid,high); //排好序顺便合并
}
}
void Merge(vector<int>&A, int p, int q, int r)
{
vector<int> L(A.begin() + p, A.begin() + q+1); //A[p......q]
vector<int> R(A.begin() + q+1, A.begin() + r+1); //A[q+1......r]
L.push_back(INT_MAX);
R.push_back(INT_MAX);
for (int i = 0, j = 0, k = p; k <= r; k++)
{
if (L[i] <= R[j])
A[k] = L[i++];
else A[k] = R[j++]; //从小到大排序
}
}
// 非递归
// merge_sort: 非递归实现 --迭代
// 非递归思想: 将数组中的相邻元素两两配对。用merge函数将他们排序,
// 构成n/2组长度为2的排序好的子数组段,然后再将他们排序成长度为4的子数组段,
// 如此继续下去,直至整个数组排好序。
// merge_sort(): 非递归实现-自底向上
// 将原数组划分为left[min...max] 和 right[min...max]两部分
void merge_sort(vector<int> &list)
{
int length = list.size();
vector<int> temp(length, 0);
int i, left_min, left_max, right_min, right_max, next;
for (i = 1; i < length; i *= 2) // i为步长,1,2,4,8……,两两合并
{
for (left_min = 0; left_min < length - i; left_min = right_max)
{
right_min = left_max = left_min + i;
right_max = left_max + i;
if (right_max > length)
right_max = length;
next = 0;
while (left_min < left_max && right_min < right_max)
temp[next++] = list[left_min] > list[right_min] ? list[right_min++] : list[left_min++];
while (left_min < left_max)
list[--right_min] = list[--left_max];
while (next > 0)
list[--right_min] = temp[--next];
}
}
}
6.快速排序
通过一趟排序将待排记录分割成独立的两部分,一部分关键字均比另一部分关键字小;再分别对这两部分进行相同的操作。
void QuickSort(vector<int>& A)
{
QSort(A,0,A.size()-1);
}
//递归
void QSort(vector<int>& A, int p, int r)
{
if (p<r)
{
int q = partition(A,p,r);
QSort(A, p, q-1);
QSort(A, q+1, r);
}
}
其中partition的实现如下:
int partition(vector<int>& A, int low, int high)
{
int key = A[low];
while(low<high)
{
while(low<high && A[high]>=key)
{
high--;
}
swap(A[low],A[high]);
while(low<high && A[low]<=key)
{
low++;
}
swap(A[low],A[high]);
}
return low;
}
//非递归的方法
//数据规模很大时,递归的算法很容易导致栈溢出,改为非递归,模拟栈操作
//最大长度为n,每次压栈时先压长度较大的,此时栈深度为logn。
template<typename Comparable>
void quicksort2(vector<Comparable> &vec,int low,int high){
stack<int> st;
if(low<high){
int mid=partition(vec,low,high);
if(low<mid-1){
st.push(low);
st.push(mid-1);
}
if(mid+1<high){
st.push(mid+1);
st.push(high);
}
//其实就是用栈保存每一个待排序子串的首尾元素下标,下一次while循环时取出这个范围
//对这段子序列进行partition操作
while(!st.empty()){
int q=st.top();
st.pop();
int p=st.top();
st.pop();
mid=partition(vec,p,q);
if(p<mid-1){
st.push(p);
st.push(mid-1);
}
if(mid+1<q){
st.push(mid+1);
st.push(q);
}
}
}
}
快速排序状态变化如下:
序号:012345678
待排:519374862
第一轮:取key=5,219374865,215374869,214375869,214357869,以5为中心分成两半:2143和7869分别再进行快排。