一、排序的分类
插入排序算法复杂度为O(n²)。因而,插入排序不适合对于数据量比较大的排序应用。
插入排序的空间复杂度为O(1)。
稳定性:稳定。
/void Insertsort(int arr[],size_t size)
// {
// for(size_t j=1;j<size;j++)
// {
// int i=j;
// while(i>0&&(arr[i]<arr[i-1]))
// {
// int key=arr[i];
// arr[i]=arr[i-1];
// arr[i-1]=key;
// i--;
// }
// }
希尔排序
基本思想:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。
图解(假设为升序):
O(N)<希尔排序的时间复杂度<O(N^2),希尔排序,当N大时,平均的时间复杂度,大约在N^1.25--1.6N^1.25之间。
希尔排序的空间复杂度为O(1)。
void ShellSort(T* a,size_t n)//希尔排序
{
assert(a);
int gap = n;//gap为所给增量
while(gap > 1)
{
//实验证明,gap=gap/3是比较优的,+1则是为了最后一次对全体数据进行插入排序
gap = gap/3 + 1;
for (size_t i = gap; i < n; ++i)
{
int end = i - gap;
T tmp = a[i];
while(end >= 0)
{
if (Compare()(tmp,a[end]))
{
a[end+gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end+gap] = tmp;
}
}
选择排序
选择排序的比较次数O(n^2),比较次数与关键字的初始状态无关,总的比较次数:
N=(n-1)+(n-2)+...+1=n*(n-1)/2。
交换次数O(n),最好情况是,已经有序,交换0次;最坏情况是,逆序,交换n-1次。
故此,选择排序的时间复杂度为O(N^2)。
选择排序的空间复杂度为O(1)。
- //C++风格的选择排序
- void SelectSort2(int* a,size_t n)
- {
- assert(a);
- int minIndex = 0;
- for (size_t i = 0; i < n - 1; ++i)
- {
- minIndex = i; //未排序区间最小数据的位置下标
- size_t pos = i + 1;//未排序区间的第一个数据下标
- while(pos < n)//选出未排序区间最小的数据
- {
- if (a[pos] < a[minIndex])
- {
- minIndex = pos;
- }
- ++pos;
- }
- swap(a[i],a[minIndex]);//将所选数据放到正确位置
- }
- }
-
- //选择排序的优化:每次既选出最大的数,也选出最小的数
- void SelectSort3(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--;//缩小区间范围
-
- }
- }
堆排序的基本思想
堆积排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
-
堆排序的时间复杂度为O(N*lgN)。
堆排序的空间复杂度为O(1)
-
//void Headadjust(int* array,int size,int parent)
快速排序
//{
// int child=parent*2+1;
// while(child<size)
// {
//
// if((child+1)<size&&array[child]<array[child+1])
// {
// child=child+1;
// }
//
//
// if (array[child]>array[parent])
// {
// std::swap(array[child],array[parent]);
// parent=child;
// child=parent*2+1;
// }
// else
// return ;
// }
//
//}
//
//int head(int* array,int size)
//{
// for(int root=((size-2)/2);root>=0;root--)
// {
// Headadjust(array, size,root);
// }
//
// int end=size-1;
// while(end>0)
// {
// std::swap(array[0],array[end]);
// Headadjust(array, end,0);
// --end;
// }
// return 0;
//}
-
说到底,快速排序就是冒泡排序的一种改进,冒泡排序是通过每一趟冒泡将最大值(最小值)放到恰当位置,而快速排序則是每趟排序从待排序区间选一个基准值(也称作枢纽值),将比它小的数据全放在其左边,将比它大的值放在其右边然后递归其左右子区间对其排序,一层层递归下去,某区直到间只剩一个数据时,停止递归,此子区间已经算是有序,继而向其上层区间返回,一层层向上返回,当首次枢纽值的左右区间均已有序时,整个排序就算完成。
//k快速排序
//Vesion1
//int MID(int* array,int left,int right)
//{
// int begin=left;
// int end=right-1;
// int key=array[end];
// while(begin<end)
// {
// while(begin<end&&array[begin]<=key)
// begin++;
//
// while(begin<end&&array[end]>=key)
// end--;
//
// if(begin<end)
// {
// std::swap(array[begin],array[end]);
// }
//
// /*if(begin!=(right-1))
// {
// std::swap(array[begin],array[right-1]);
// }*/
// }
// return end;
//}
//void Quicksort(int array[],int left,int right)
//{
// if(left<right)
// {
// int b=left;
int e=right-1;
int key=array[e];
while(b<e)
{
while(b<e&&array[b]<=key)
b++;
if(b<e)
array[e--]=array[b];
while(b<e&&array[e]>=key)
e--;
if(b<e)
array[b++]=array[e];
}
array[b]=key;
Quicksort(array,left,b);
Quicksort(array,b+1,right);
}
}
//int GetmidIndex(int array[],int left,int right)
//{
//
// int mid=(left+right)/2;
// if(array[left]<array[right])
// {
// if(array[left]>array[mid])
// return left;
// else if(array[right]<array[mid])
// return right;
// else
// return mid;
// }
// else
// {
// if(array[right]>array[mid])
// return right;
// else if(array[left]<array[mid])
// return left;
// else
// return mid;
// }
//}
//int Quicksort(int array[],int left,int right)
//{
//
// int mid = GetmidIndex(array, left, right);
// if (mid != right)
// {
// swap(array[mid], array[right]);//将中间值交换到此范围最右边,提高效率
// }
// int key = array[right];//一般将最
//
// int begin=left;
// int end=right;
// key=array[end];
// while(begin<end)
// {
//
// while(begin<end&&array[begin]<=key)
// begin++;
//
// while(begin<end&&array[end]>=key)
// end--;
//
// if(begin<end)
// std::swap(array[begin],array[end]);
// }
// if (array[right] <array[begin])
// {
// swap(array[begin], array[right]);
// return begin;
// }
// else
// return right;
//
//}
//void QuickSort1(int *array, int left, int right)//递归
//
//{
//
// if (left < right)
// {
// int boundary = Quicksort(array, left, right);//关键字所在位置的下标
// QuickSort1(array, left, boundary-1);
// QuickSort1(array, boundary + 1, right);
// }
// }
//
//void sort(int* array,int left,int right)
//{
// stack<int> s;
// if(left<right)
// {
// int mid=Quicksort(array,left, right);
// if(left<mid-1)
// {
// s.push(left);
// s.push(mid-1);
// }
// if(mid+1<right)
// {
// s.push(mid+1);
// s.push(right);
// }
// while(!s.empty())
// {
// int end=s.top();
// s.pop();
// int begin=s.top();
// s.pop();
// mid=Quicksort(array,begin, end);
// if(begin<mid-1)
// {
// s.push(begin);
// s.push(mid-1);
// }
// if(mid+1<end)
// {
// s.push(mid+1);
// s.push(end);
// }
//
// }
// }
//}
一、归并排序
1、基本思想
归并排序(Merge sort,台湾译作:合并排序)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
算法//
void Merge(int* array,int left,int right,int* temp)
{
int mid=(left+right)/2;
int b1=left;
int end1=mid;
int b2=mid+1;
int end2=right;
int index=left;
while(b1<=end1&&b2<=end2)
{
if(array[b1]<=array[b2])
{
temp[index++]=array[b1++];
}
else
{
temp[index++]=array[b2++];
}
}
while(b1<=end1)
{
temp[index++]=array[b1++];
}
while(b2<=end2)
{
temp[index++]=array[b2++];
}
}//void _sort(int* array,int left,int right,int* temp)
// {
// if(left<right)
// {
// int mid=(left+right)/2;
// _sort(array,left,mid,temp);
// _sort(array,mid+1,right,temp);
// Merge(array,left,right,temp);
// memcpy(array+left,temp+left,(right-left+1)*sizeof(array[0]));
// }
//
// }
原理:
1>按照类似快速排序的方法递归地将待排序序列依次划分为两个区间,区间只剩一个数停止划分;
2>如果一个区间只剩一个数,我们可将其看做有序区间,然后对左右两个小区间进行归并,归并后仍要保持区间的有序性;
3>同2>提到的方法我们每次将两个有序的子区间归并为一个大的有序区间,并返回给上一层递归;
4>直到所有划分的区间归并为一个有序序列,归并排序就算完成。
一、计数排序
1、基本思想
给定一组要排序的序列,找出这组序列中的最大值,然后开辟一个最大值加1大小的数组,将这个数组里面的元素全部置零,然后用这个数组统计出要排序的序列中各个元素出现的次数。等到统计完成的时候,排序就已经完成了。
图解(假设升序):
2、算法执行步骤:1>找出待排序序列里的最大值max;
2>开辟一个大小为max+1的临时数组tmp ,将数组元素的所有初值赋为0;
3>遍历待排序序列,将序列中出现值作为下标,对tmp 数组的对应位置数据进行++;
4>上述操作完成后,tmp中统计了每个数据在待排序列中出现的次数;
3>最后,将tmp数组里值不为0的所有下标拷进原序列(注意同一个下标可能有多个重复值,都要进行拷贝,不能遗漏),排序就算完成。
3、计数排序的优化
从上面的例子中可以看出,3是待排序序列的最小值,在tmp数组中3 之前的位置并没有什么卵用,那么基于节省空间的角度进行考虑,我们可以开辟更小的临时数组统计次数,同样能完成排序。
比如要对1000,999,1008这三数进行排序,按照普通的方法前998个空间都被浪费掉了,所以这时候我们对它进行优化。找出要排序的这组元素中的最大值和最小值,这样就确定了这组元素的范围,然后开辟这个范围加1大小的数组,然后再将要排序的元素映射到这个新开辟的数组中就可以了。
结论:计数排序适用于数据比较集中的序列排序。
4、时间复杂度&&空间复杂度
计数排序是一种非比较的排序方法,它的时间复杂度是O(N+K),空间复杂度是0(K),其中K是要排序的数组的范围。可以看出计数排序是一种以空间呢换取时间的方法。如果当K>N*logN的时候,计数排序就不是好的选择了,因为基于比较排序的算法的下限是O(N*logN)。
5、代码实现
二、基数排序
1、基数排序的分类
LSD-- Least Significant Digit first(从低位向高位排)
MSD-- Most Significant Digit first(从高位向低位排)
由于原理差不多,此文只针对LSD进行分析。
-
2、基本思想
基数排序又称"桶子法",它从低位开始将待排序的数按照这一位的值放到相应的编号为0到9的桶中。等到低位排完之后得到一个序列,再将这个序列按照次低位的大小进入相应的桶中。以此类推,直到将所有的位都排完之后,这组数就已经有序了。
图解(假设升序):
3、算法执行步骤
1》统计待排序序列中最大的数是几位数;
2》开辟一个与原数组大小相同的临时数组tmp;
3》用一个count数组统计原数组中某一位(从个位向高位统计)相同的数据出现的次数;
4》用一个start数组计算原数组中某一位(从个位向高位计算)相同的数据出现的位置;
5》将桶中数据从小到大(由顶至底)用tmp数组收集起来;
6》循环3》4》5》直到所有位都被统计并计算过,然后用tmp收集起来;
7》将tmp数组的数据拷回原数组,排序完成。
图解:
4、时间复杂度&&空间复杂度
基数排序是一种非比较排序,它的时间复杂度是O(N*digit),其中digit是这组待排序中的最大的数的数量级。基数排序的空间复杂度就是在分配元素时,使用的桶空间,所以基数排序的空间复杂度是O(N)。它是一种稳定的排序方法。
5、代码实现
-