插入排序
一、直接插入排序
思路:选择一个有序区间,然后依次拿出后面的数往有序区间里面插入
第一步:第一个数即为一个有序区间
第二步:取出无序区间的第一个数,从有序区间最后一个数开始依次向前比较,判断大小
第三步:为有序区间的数挪出合适的位置并放入,直到无序区间数据取完,整个序列排序完成
code:
//这里以升序为例:
#include <iostream>
using namespace std;
#include <assert.h>
//升序
void Insertsort(int *a,int n)
{
assert(a);
for(int i=1; i<n; i++)
{
int end = i-1;//有序区间最后一个元素的下标
int tmp = a[i];
while(end>=0)
{
if(a[end]>tmp)
{
a[end+1] = a[end];//为tmp挪出合适的位置
--end;
}
else{
break;
}
}
a[end+1] = tmp;
}
}
void TestInsertSort()
{
int a[] = {5,4,3,2,1};
int len = sizeof(a)/sizeof(a[0]);
Insertsort(a,len);
for(int i=0; i<len; i++)
{
cout<<a[i]<<" ";
}
cout<<endl;
}
int main()
{
TestInsertSort();
return 0;
}
为了代码的可复用性,我们可以写两个仿函数来完成升序和降序的工作
code:
#include <iostream>
using namespace std;
#include <assert.h>
//升序
template <class T>
struct Greate
{
bool operator()(const T& a1,const T& a2)
{
return a1 > a2;
}
};
//降序
template <class T>
struct Less
{
bool operator()(const T& a1,const T& a2)
{
return a1 < a2;
}
};
//升序
template <class T,class Compare>
void Insertsort(T *a,T n)
{
assert(a);
for(int i=1; i<n; i++)
{
int end = i-1;//有序区间最后一个元素的下标
int tmp = a[i];
while(end>=0)
{
if(Compare()(a[end],tmp))
{
a[end+1] = a[end];//为tmp挪出合适的位置
--end;
}
else{
break;
}
}
a[end+1] = tmp;
}
}
void TestInsertSort()
{
int a[] = {5,4,3,2,1};
int len = sizeof(a)/sizeof(a[0]);
Insertsort<int,Greate<int>>(a,len);
for(int i=0; i<len; i++)
{
cout<<a[i]<<" ";
}
cout<<endl;
}
int main()
{
TestInsertSort();
return 0;
}
时间复杂度空间复杂度分析:
最坏情况:开始的序列是与要排的序列相反的,比如你要拍降序,但是他本来就是升序,那么要进行比较n(n-1)/2,所以时间复杂度为O(n^2)
消耗的空间位常数个,所以时间复杂度为O(1)
适用于数据量很小的序列,比如小于一千。
二、希尔排序
希尔排序是对插入排序的一种优化。在序列相反的情况下,先对序列进行预排序,使序列接近有序,再进行插入排序。
两点:
1、插入排序对于几乎已经排好序的序列操作时,效率很高,能达到线性排序的效率
2、插入排序一次只能讲数据移动一位,效率是很低的。
对此我们做出优化为希尔排序
思路:先将整序列分成若干个子序列(由相隔的某个增量完成),分别对子序列进行插入排序,待整个序列基本有序,再对整体进行插入排序。
code:
//希尔排序
//升序
template <class T>
struct Greate
{
bool operator()(const T& a1,const T& a2)
{
return a1 > a2;
}
};
//降序
template <class T>
struct Less
{
bool operator()(const T& a1,const T& a2)
{
return a1 < a2;
}
};
template <class T,class Compare>
void ShellSort(T* a,int n)
{
assert(a);
int gap = n; //gap为所给的增量
while(gap > 1)
{
gap = gap/3 +1; //实验证明gap = n/3比较优,加1是为了最后对整体排序
for(int i=gap; i<n; i+=gap)
{
int end = i - gap;
int tmp = a[i];
while(end >=0)
{
if(Compare()(a[end],tmp))
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end+ gap] = tmp;
}
}
}
void TestShellSort()
{
int a[] = {1,2,3,4,5,6};
int len = sizeof(a)/sizeof(a[0]);
ShellSort<int,Greate<int>>(a,len);
for(int i = 0; i<len; i++)
{
cout<<a[i]<<" ";
}
cout<<endl;
}
int main()
{
//TestInsertSort();
TestShellSort();
return 0;
}
时间复杂度&空间复杂度分析
时间复杂度:
因为是插入排序的优化: 大约在O(N^1.25~1.6N^1.25)
空间复杂度为O(1)
交换排序
1、冒泡排序
思路:
对一个序列的数据进行两两比较,比如升序,从开始对数据进行比较,如果前者大于后者进行交换,重复此工作一直到最后两个数交换,第一趟完成,然后缩小排序区间,从头开始第二趟冒泡比较,重复比较直至序列有序
冒泡排序的优化,对已经有序的区间不需要再重复比较,设置标志位,详情见代码:
code:
//冒泡排序
void Bubble(int* a,int n)
{
int i =0,j=0;
int flag=0;//设置标志位控制循环次数
for(i=0;i<n-1;i++)//因为要交换最后一个数和倒数第一个数所以只到n-1
{
flag = 0;
for(j=0;j<n-1;j++)//控制比较的次数
{
if(a[j] > a[j+1]) //升序
{
int tmp = a[j];
a[j] = a[j+1];
a[j+1] = tmp;
flag = 1;
}
}
if(flag == 0)
{
break;
}
}
}
void TestBubble()
{
int a[] = {6,5,4,3,2,1};
int n = sizeof(a)/sizeof(a[0]);
Bubble(a,n);
for(int i=0; i<n;i++)
{
cout<<a[i]<<" ";
}
cout<<endl;
}
int main()
{
//TestInsertSort();
//TestShellSort();
TestBubble();
return 0;
}
时间复杂度&空间复杂度
时间复杂度:一个n长序列需要冒泡n-1趟,递减每趟需要冒泡n-1-i(i为递增的),时间复杂度为O(N^2)
空间复杂度:O(1)
场景:冒泡排序是一种很稳定的排序
2、快速排序
思路:是对冒泡排序的一种优化,在序列中先选出一个基准值,比基准值大的放基准值右边,比基准值小的放基准值左边,然后同样的方法递归去划分左区间和右区间,直到某区间只剩下一个数据时停止递归,此区间已经有序,然后向上层区间返回,当最开始的基准值的左右区间已经有序则整个序列排序完成
code:
#include <iostream>
using namespace std;
#include <stack>
#include <assert.h>
int GetMidIndex(int *a, int left, int right)
{
int mid = left + ((right - left) >> 1);
if (a[left] < a[mid])
{
if (a[mid] < a[right])
{
return mid;
}
else if (a[left] < a[right])
{
return right;
}
else
{
return left;
}
}
else//a[mid] < a[left]
{
if (a[mid] > a[right])
{
return mid;
}
else if (a[right] < a[left])
{
return right;
}
else
return left;
}
}
//左右指针法
int PartSort1(int* a, int left, int right)
{
int mid = GetMidIndex(a, left, right);
swap(a[mid], a[right]);
int key = right;
while (left < right)
{
while (left < right &&a[left] <= a[key])
{
++left;
}
while (left < right && a[right] >= a[key])
{
--right;
}
//交换左右指针所指的值
if (a[left] != a[right])
{
swap(a[left], a[right]);
}
}
//将key值放到正确位置上
swap(a[left], a[key]);
return left;
}
//挖坑法
int ParSort2(int* a, int left, int right)
{
int mid = GetMidIndex(a, left, right);
swap(a[mid], a[right]);
int key = a[right];
int blank = right;
while (left < right)
{
while (left < right && a[left] <= key)
{
++left;
}
a[blank] = a[left];
blank = left;
while (left < right && a[right] >= key)
{
--right;
}
a[blank] = a[right];
blank = right;
}
a[blank] = key;
return blank;
}
//前后指针法
int PartSort3(int* a, int left, int right)
{
int mid = GetMidIndex(a, left, right);
swap(a[mid], a[right]);
int key = right;
int cur = left;
int prev = cur - 1;
while (cur != right)
{
if (a[cur] < a[key] && a[++prev] != a[cur])
{
swap(a[prev], a[cur]);
}
++cur;
}
swap(a[++prev], a[cur]);
return prev;
}
//三数取中法
//元素小于13个进行直接插入排序进行优化
void InsertSort(int* a, size_t n)
{
int end = 0;
for (size_t i = 1; i < n; ++i)
{
int tmp = a[i];
int end = i - 1;
while (end >= 0)
{
if (a[end]>tmp)
{
a[end+1] = a[end] ;
}
else
{
break;
}
}
a[end + 1] = tmp;
}
}
void QuickSortNoR(int* a, int left, int right)
{
assert(a);
stack<int> s;
s.push(right);
s.push(left);
while (!s.empty())
{
int start = s.top();
s.pop();
int end = s.top();
s.pop();
//前后指针法实取到div
int div = PartSort3(a, start, end);
if (start < div - 1)
{
s.push(div - 1);
s.push(start);
}
if (end>div + 1)
{
s.push(end);
s.push(div + 1);
}
}
}
//递归实现
void QuickSort(int* a, int left, int right)
{
assert(a);
if (left < right)
{
int kiv = PartSort1(a, left, right);
if (right - left < 2)
{
InsertSort((a + left), (right - left + 1));
}
else
{
QuickSort(a, left, kiv-1);
QuickSort(a, kiv + 1, right);
}
}
}
void Print(int* a, size_t n)
{
for (size_t i = 0; i < n; i++)
{
cout << a[i] << " ";
}
cout << endl;
}
void TestQuickSort()
{
int a[] = { 2, 0, 4, 9, 6, 5, 3, 1, 7, 8 };
size_t sz = sizeof(a) / sizeof(a[0]);
QuickSort(a, 0, sz - 1);
Print(a, sz);
}
/*快速排序的平均时间复杂度为O(N*logN)
*最坏情况是当分组重复生成一个空序列的时候变成O(N*N)
*优化避免最坏情况:三数取中法->每次选择基准数选择面值居中的元素
*小区间优化:当需要排序的元素小于13个时进行插入排序
*/
时间复杂度&空间复杂度
时间复杂度:快速排序是一种分治法,优化后的时间复杂度为O(N*logN)
空间复杂度为 O(N)
场景:快速排序是一种不稳定的排序,但是它是已知的最快的排序算法
选择排序
一、选择排序
思路:选一个数据放到合适的位置,缩小需要排序的区间,比如选择区间中最小的数据放到第一个位置,缩小区间继续选择次小的放到第二个位置…….直到区间有序
code:
//选择排序
void SelectSort(int*a ,size_t n)
{
assert(a);
int left = 0;//未排序区间的左下标
int right = n-1;//未排序区间的右下标
while(left < right)
{
int minIndex = left;//未排序区间最小数据的位置下标
int maxIndex = right;//未排序区间最大数据的位置小标
//选出最大和最小数据的下标
for(int i = left; i<=right;++i)
{
if(a[i] < a[minIndex])
{
minIndex = i;
}
if(a[i] > a[maxIndex])
{
maxIndex = i;
}
}
//修正:最大值在最小值或最小值在最大位置
swap(a[maxIndex],a[right]);
if(minIndex == right)
{
minIndex = maxIndex;
}
swap(a[minIndex],a[left]);
left++;
right--;
}
}
void TestSelectSort()
{
int a[] = {9,5,4,2,3,6,8,7,1,0};
size_t sz = sizeof(a)/sizeof(a[0]);
//SelectSort1(a,sz);
//SelectSort2(a,sz);
SelectSort(a,sz);
for(int i=0; i<sz; i++)
{
cout<<a[i]<<" ";
}
cout<<endl;
}
int main()
{
//TestInsertSort();
//TestShellSort();
//TestBubble();
//testFastSort();
TestSelectSort();
return 0;
}
时间复杂度&空间复杂度:
时间复杂度:最坏为O(N^2)
空间复杂度为O(1)
2、堆排序
思路:堆排序是利用了堆这种数据结构的特性产生的一种算法,堆结构近似于一棵完全平衡二叉树。
首先对一个序列进行建立大顶堆,即数的父节点总是大于左右子节点的结构,建立好大顶堆之后,用堆顶和最后一个数据进行交换,此时形成了一个新的无序序列和一个只有一个元素的有序序列,对无序序列在进行调整形成大顶堆,再用堆顶和无序序列的最后一个元素交换,又形成了新的无序序列和有两个元素的有序序列,依次进行调整,交换,直至无序序列剩下一个元素为止。
图解:
code:
//堆排序
//堆调整:将堆的末端子节点做调整,使子节点永远小于父节点
void AdjustDown(int* a,int n,int pos)
{
int parent = pos;//父节点的下标
int child = parent*2 + 1;//左孩子的下标
while(child<n)
{
//选左孩子中较大的
if((child+1 <n) && a[child] < a[child+1])
{
++child;
}
if(a[child] > a[parent])
{
swap(a[child],a[parent]);
//向下更新父节点和孩子节点
parent = child;
child = parent*2 +1;
}
else
{
break;
}
}
}
//默认升序,建立大堆
void HeaSort(int* a,int n)
{
assert(a);
//从最后一个非叶子节点开始向下调整
for(int i = (n-2)>>1; i>=0;--i)
{
AdjustDown(a,n,i);//创建最大堆,将堆所有数据重新排序
}
//缩小范围
for(int i=n-1; i>0; --i)
{
swap(a[0],a[i]);//将根节点和最后一个数据交换
//在进行建大堆
AdjustDown(a,i,0);
}
}
void TestHeapSort()
{
int a[] = {5,4,3,2,1};
int len = sizeof(a)/sizeof(a[0]);
HeaSort(a,len);
for(int i=0; i<len; i++)
{
cout<<a[i]<<" ";
}
cout<<endl;
}
int main()
{
//TestInsertSort();
//TestShellSort();
//TestBubble();
//testFastSort();
//TestSelectSort();
TestHeapSort();
return 0;
}
升序,建大堆
降序,建小堆
时间复杂度&空间复杂度
时间复杂度:因为堆近似一颗平衡二树,不断进行交换,向下调整建堆,时间复杂度为O(N*logN)
空间复杂度:因为占据有限个空间,空间复杂度为O(1)
归并排序
思路:归并排序是建立在归并操作上的一种排序算法,采用分治法的思想。
1>按照类似快速排序的方法递归地将待排序序列依次划分为两个区间,区间只剩一个数停止划分;
2>如果一个区间只剩一个数,我们可将其看做有序区间,然后对左右两个小区间进行归并,归并后仍要保持区间的有序性;
3>同2>提到的方法我们每次将两个有序的子区间归并为一个大的有序区间,并返回给上一层递归;
4>直到所有划分的区间归并为一个有序序列,归并排序就算完成。
code:
void Merge(int* a,int* tmp,int left,int mid,int right)
{
int begin1 = left; //左区间的左边界
int end1 = mid; //左区间的右边界
int begin2 = mid + 1; // 有区间的左边界
int end2 = right; //右区间的右边界
int index = left; //中间数组的下标
while(begin1<=end1 && begin2 <=end2)
{
//将左右区间较小的值放入中间数组
if(a[begin1] < a[begin2])
{
tmp[index++] = a[begin1++];
}
else{
tmp[index++] = a[begin2++];
}
}
//如果左区间还有数据,直接放到中间数据的后边
while(begin1<=end1)
{
tmp[index++] = a[begin1++];
}
//如果有区间还有数据,直接放到中间数组的后面
while(begin2 <=end2)
{
tmp[index++] = a[begin2++];
}
//将中间数组拷贝回原数组
memcpy(a,tmp,index*sizeof(int));
}
void _MerSort(int* a,int* tmp,int left,int right)
{
//当区间只有一个数或没有数据时递归返回
if(left >= right)
{
return;
}
int mid = left + ((right-left)>>1);//划分左右区间的中间值
_MerSort(a,tmp,left,mid);
_MerSort(a,tmp,mid+1,right);
//对左右两个有序区间进行归并
Merge(a,tmp,left,mid,right);
}
void MergeSort(int* a,int n)
{
assert(a);
int* tmp = new int[n];
_MerSort(a,tmp,0,n-1);
delete[] tmp;
}
void testMergesort()
{
int a[] = {6,5,4,3,2,1};
int len = sizeof(a)/sizeof(a[0]);
MergeSort(a,len);
for(int i =0; i<len;i++)
{
cout<<a[i]<<" ";
}
cout<<endl;
}
int main()
{
//TestInsertSort();
//TestShellSort();
//TestBubble();
//testFastSort();
//TestSelectSort();
//TestHeapSort();
testMergesort();
return 0;
}
归并排序非递归的code
void Merge_sort(int *a, int length)
{
int i, begin1, end1, begin2, end2, index;
int *tmp = new int[length];
for (i = 1; i < length; i *= 2)
{
for (begin1 = 0; begin1 < length - i; begin1 = end2)
{
begin2 = end1 = begin1 + i;
end2 = end1 + i;
if (end2 > length)
end2 = length;
index = 0;
while (begin1 < end1 && begin2 < end2)
tmp[index++] = a[begin1] > a[begin2] ? a[begin2++] : a[begin1++];
while (begin1 < end1)
a[--begin2] = a[--end1];
while (index > 0)
a[--begin2] = tmp[--index];
}
}
delete []tmp;
}
时间复杂度&空间复杂度
时间复杂度:采用分而治之的思想,时间复杂度为O(N*logN)
空间复杂度:开辟新空间,空间复杂度为O(N)