|
Andy Niu Help
1.0.0.0
|
变量 | |
| 冒泡排序 | |
| 插入排序 | |
| 希尔排序 | |
| 选择排序 | |
| 快速排序 | |
| 堆排序 | |
| 归并排序 | |
详细描述
变量说明
| 冒泡排序 |
1、冒泡排序,外部循环,执行趟数,第一趟,对集合相邻两个比较,大的数向下沉,结束后,最大的数在最下面,
第二趟,从头部开始,尾部减1,找出次大的数。
2、时间复杂度:
平均情况【n*n】
最好情况【n*n】
最坏情况【n*n】
4、辅助空间:
1
5、稳定的排序算法
template <typename T>
void BubbleSort(std::vector<T>& vec)
{
for(int i=0;i<vec.size();++i)
{
for(int j=0;j<vec.size()-i-1;++j)
{
if(vec[j] > vec[j+1])
{
Swap(vec[j],vec[j+1]);
}
}
}
}
1、上面的冒泡排序存在问题,比如已经有序的情况,0 1 2 3 4 5,每一趟都需要遍历两两比较。 2、可以优化,如果这一趟没有执行swap,可以断定第二趟也不会执行swap,因此优化如下:
template <typename T>
void BubbleSort_2(std::vector<T>& vec)
{
bool isNeedContinue = true; // 初始化需要进行下一趟
for(int i=0;i<vec.size()&&isNeedContinue;++i)
{
isNeedContinue = false; // 假定不需要下一趟
for(int j=0;j<vec.size()-i-1;++j)
{
if(vec[j] > vec[j+1])
{
Swap(vec[j],vec[j+1]);
isNeedContinue = true; // 存在交换,还要进行下一趟
}
}
}
}
| 堆排序 |
1、堆排序分为两步:构建堆和去掉头部,重新调整堆。
2、堆排序的思路:堆是逻辑上的完全二叉树,通过下标确定父子关系,a[k]的两个孩子a[2k+1]和a[2k+2].
堆化需要知道当前下标和集合的长度,对当前下标和子节点堆化。
3、构建堆,从第一个非叶子结点,开始,向前递进。
去除头部,重新调整堆,当前下标是0,长度是原来的长度减1。
4、时间复杂度:
平均情况【nlogn】
最好情况【nlogn】
最坏情况【nlogn】
5、辅助空间:
1
6、不稳定的排序算法。
// curIndex 非叶子结点的下标,对它和子节点堆化
// 堆化的长度
void HeapAdjust(vector<int>& vec, int curIndex,int len)
{
while(curIndex<len)
{
int leftChildIndex = 2*curIndex+1;
int rightChildIndex = 2*curIndex+2;
if(leftChildIndex < len) //左孩子存在
{
if(rightChildIndex < len) //右孩子存在
{
// 两个孩子中找到一个大的
if(vec[leftChildIndex] >= vec[rightChildIndex]) // 左孩子大
{
// 和左孩子比较
if(vec[curIndex] >= vec[leftChildIndex]) // 堆正确
{
break;
}
else // 把左孩子提拔上来
{
swap(vec[curIndex],vec[leftChildIndex]);
curIndex = leftChildIndex;
}
}
else
{
// 和右孩子比较
if(vec[curIndex] >= vec[rightChildIndex]) // 堆正确
{
break;
}
else // 把右孩子提拔上来
{
swap(vec[curIndex],vec[rightChildIndex]);
curIndex = rightChildIndex;
}
}
}
else
{
// 和左孩子比较
if(vec[curIndex] >= vec[leftChildIndex]) // 堆正确
{
break;
}
else // 把左孩子提拔上来
{
swap(vec[curIndex],vec[leftChildIndex]);
curIndex = leftChildIndex;
}
}
}
else
{
break;
}
}
}
void HeapSort(vector<int>& vec)
{
// 从最后一个非叶子节点,向前,堆化
int start = vec.size()/2 -1; // 最后一个非叶子结点
for(int i = start; i>=0; --i)
{
HeapAdjust(vec,i,vec.size());
}
// 头和尾交换,从头到尾部前一个堆化
for(int i =0;i< vec.size()-1;++i) // 最后一个元素不需要堆化
{
swap(vec[0],vec[vec.size()-i-1]);
HeapAdjust(vec,0,vec.size()-i-1);
}
}
| 希尔排序 |
1、对于插入排序,如果已经基本有序,算法效率很高。
2、所谓基本有序,也就是对于整个序列,小的数在前面,中间的数在中间,大的数在后面。
如何使它基本有序呢?分组,跳跃式的分组。考虑9个元素,分成3组,分别为:
a[0],a[3],a[6]
a[1],a[4],a[7]
a[2],a[5],a[8]
每一组插入排序,就使得基本有序。然后缩小间隔,直到为1
3、也就是说,希尔排序有三层循环,外层循环缩小间隔,因为是插入排序,前面的一个元素有序,
中间循环从每个分组的第二个元素开始,遍历a[3],a[4]...,内层循环记录当前下标和元素,和target比较,
挖前面的数,填后面的坑。
虽然希尔排序有三层循环,但是效率高。外层循环缩小,下降很快,中间是n,内层循环跳跃是 n/interval,
interval不是常数,会变化。希尔排序的时间复杂度 小于 n*n
4、时间复杂度:
平均情况【nlogn到n*n】
最好情况【n^1.3 】n的1.3次方
最坏情况【n*n 】
5、辅助空间:
1
6、不稳定的排序算法。
void ShellSort(vector<int>& vec)
{
int interval = vec.size();
do
{
interval = interval/3+1;
for(int i = interval;i<vec.size();++i) // 从每个分组的第二个元素
{
int curIndex = i; //记录当前下标
int target = vec[i];//记录当前元素,挖数,留下一个坑
// 没有走到分组的第一个元素,并且目标小
while(curIndex>=interval && target<vec[curIndex-interval])
{
vec[curIndex]=vec[curIndex-interval]; // 挖前面的数,填后面的坑
curIndex-=interval; //分组的前一个元素
}
vec[curIndex] = target; // 补平遗留的坑
}
}while(interval>1); // 缩小间隔,直到为1
}
| 归并排序 |
1、归并排序的思路:使用递归,调用MSort,MSort内部使用一个临时的集合,对前一段归并,对后一个归并,然后Merge
2、时间复杂度:
平均情况【nlogn】
最好情况【nlogn】
最坏情况【nlogn】
3、辅助空间:
n
4、稳定的排序算法。
// 对于 aVec, 范围[first,mid)和范围[mid,last)都是有序的,将它们归并bVec[first,last)
void Merge(vector<int>& aVec,vector<int>& bVec,int first,int mid,int last)
{
int i=first; // 前一段的开始下标
int j=mid; // 后一段的开始下标
int k=first; // 目标集合的开始下标
while(i<mid && j<last) // 两段都没有走到结尾
{
if(aVec[i]<aVec[j]) // 前面的小
{
bVec[k++] = aVec[i++]; // 从前一段取元素
}
else
{
bVec[k++] = aVec[j++]; // 从后一段取元素
}
}
// while退出后,只有一段还有剩余元素,哪个段的最大值大,哪个段有剩余元素
// 复制前一段的剩余元素
while(i<mid)
{
bVec[k++] = aVec[i++];
}
// 复制后一段的剩余元素
while(j<last)
{
bVec[k++] = aVec[j++];
}
}
// vecRef和vec是同一个集合的引用,把vec的[first,last)归并到vecRef
void MSort(vector<int>& srcVec,vector<int>& dstVec,int first,int last)
{
if(first == last-1)
{
dstVec[first]=srcVec[first];
}
else
{
int mid = (first+last)/2;
// 栈上分配一个临时的集合
vector<int> tmpVec;
tmpVec.resize(dstVec.size());
// vec的[first,mid) 归并到vecCpy的[first,mid)
MSort(srcVec,tmpVec,first,mid);
// vec的[mid,last) 归并到vecCpy的[mid,last)
MSort(srcVec,tmpVec,mid,last);
// vecCpy的[first,mid)和[mid,last)有序,进行Merge到vecRef
Merge(tmpVec,dstVec,first,mid,last);
}
}
void MergeSort(vector<int>& vec)
{
MSort(vec,vec,0,vec.size());
}
1、使用递归,导致空间上的浪费,每次都要一个临时对象。 2、递归的思路是先自上而下,再自下而上。 3、能不能直接自下而上,相邻合并,再相邻合并。
void MergePass(vector<int>& srcVec,vector<int>& dstVec,int interval)
{
int i =0;
int len = dstVec.size();
while(i< len-2*interval+1)
{
Merge(srcVec,dstVec,i,i+interval,i+2*interval);
i = i+2*interval;
}
if(i<len-interval) // 最后剩余2个序列,长度不同
{
Merge(srcVec,dstVec,i,i+interval,dstVec.size());
}
else // 最后剩余1个序列
{
for(int j = i;j<len;++j)
{
dstVec[j] = srcVec[j];
}
}
}
void MergeSort_2(vector<int>& vec)
{
vector<int> tmpVec;
tmpVec.resize(vec.size());
int i = 1;
while(i<vec.size())
{
MergePass(vec,tmpVec,i); // 把vec相邻,间隔为1的两块,归并到tmpVec
i*=2;
MergePass(tmpVec,vec,i); // 把tmpVec相邻,间隔为2的两块,归并到vec
i*=2; // 为下一趟做准备
}
}
| 快速排序 |
1、快速排序,调用递归方法。因为是折半递归,效率并不差。如果是线性递归,比如斐波那契数列,效率很低。
2、快速排序思路:递归调用,前后相遇,结束递归。没有相遇,先从前面挖个数,留下一个坑,然后循环,
一个循环里,后面挖个数填前面的坑,然后前面挖个数,填后面的坑,循环直到相遇。
相遇之后,跳出循环,填上遗留的坑,对前后两块递归调用。
3、时间复杂度:
平均情况【nlogn】
最好情况【nlogn】,每次都刚好分成均匀的两块
最坏情况【n*n 】,每次都分成极端不均衡的两块,1和n-1个
4、辅助空间:
递归造成的栈空间使用,递归的深度最好情况 logn,最坏情况n,因此辅助空间 logn到n
5、比较和交换是跳跃式的,因此是不稳定的排序算法。
template <typename T>
void QSort(std::vector<T>& vec,int left,int right)
{
int i = left; // 左边
int j = right-1;// 右边
if(i >= j) // 已经相遇,结束递归
{
return;
}
T target(vec[i]); // 从前面挖个数,留下一个坑
while(i<j) // 前后夹击,直到相遇
{
while(i<j && vec[j]>target) // 从后向前,找出一个小的
{
--j;
}
if(i<j) // 没有相遇
{
vec[i++] = vec[j]; //从后面挖个数,填上前面的坑
}
while(i<j && vec[i]<target) // 从前向后,找出一个大的
{
++i;
}
if(i<j) // 没有相遇
{
vec[j--]=vec[i]; //从前面挖个数,填上后面的坑
}
}
vec[i] = target; // 填上遗留的坑
QSort(vec,left,i);
QSort(vec,i+1,right);
}
template <typename T>
void QuickSort(std::vector<T>& vec)
{
QSort(vec,0,vec.size());
}
| 插入排序 |
1、插入排序,前面的元素有序,当前元素进行插入。外部循环,记录当前下标,把当前元素挖走,留下一个坑,
对于前面有序的子序列,从后向前,找出比它小的,比它大,从前面挖数,填后面的坑,直到头部或者找到位置,
最后填上遗留的坑。
2、时间复杂度:
平均情况【n*n】
最好情况【n 】,已经有序,每次都不需要从前面挖数,填后面的坑。
最坏情况【n*n】,倒序排列,每次都要从前面挖数,填后面的坑,一直到最前面。
3、辅助空间:
1
4、稳定的排序算法。
template <typename T>
void InsertSort(std::vector<T>& vec)
{
for(int i =1;i<vec.size();++i)
{
int curIndex = i; // 记录当前的下标
T target(vec[i]); // 挖个数,留下一个坑
while(curIndex >0 && target<vec[curIndex-1]) // 当前元素小,向前移动
{
vec[curIndex] = vec[curIndex -1]; // 从前面挖数,填后面的坑
--curIndex; // 递进
}
vec[curIndex] = target; // 填上遗留的坑
}
}
| 选择排序 |
1、选择排序,外部循环,假定当前下标就是最小值的下标,内部循环从下一位开始遍历,找出一个最小值的下标。
2、内部循环退出,和假定不一致,进行交换。
3、时间复杂度:
平均情况【n*n】
最好情况【n*n】
最坏情况【n*n】
因为,假定当前下标的元素就是最小值,必须遍历内部循环才确定。
4、辅助空间:
1
5、稳定的排序算法。
template <typename T>
void SelectSort(std::vector<T>& vec)
{
for(int i =0;i<vec.size();++i)
{
int minIndex = i; // 假定当前下标就是最小值的下标
for(int j =i+1;j<vec.size();++j)
{
if(vec[minIndex]>vec[j])
{
minIndex = j; // 找出最小值的下标
}
}
if(minIndex != i) // 和假定不一致,交换元素
{
Swap(vec[minIndex],vec[i]);
}
}
}
Copyright (c) 2015~2016, Andy Niu @All rights reserved. By Andy Niu Edit.