排序算法总结:
1、排序的稳定性:对于两个相等的元素,排序后的顺序和排序前一致,该算法是稳定的,反之亦然
2、内排序和外排序:内排序:排序过程中,所有数据都在内存中;数据量大的,需要内外存之间多次交换数据的就是外排序
内排序的性能:时间性能,辅助空间,算法复杂度
#define MAXSIZE 10
typedef struct
{
int r[MAXSIZE + 1]; // r[0]存放哨兵或者临时变量
int len;
}SqList;
void swap(SqList *L, int i, int j)
{
int temp = L->r[i];
L->r[i] = L->r[j];
L->r[j] = temp;
}
一、冒泡排序 O(n^2)
依次选择序列中的最小或者最大值,将其按从小到大或者从大到小的顺序排列。
void BubbleSort(SqList *L)
{
int i, j;
for(i = 1; i < L->len; i++)
{
for(j = i; j < L->len - 1; j++)
{
if(L->r[j] > L->r[j + 1])
swap(L, j, j+1);
}
}
}
for (i=0; i<n-1; ++i) //比较n-1轮
{
for (j=0; j<n-1-i; ++j) //每轮比较n-1-i次,
{
if (a[j] < a[j+1])
{
buf = a[j];
a[j] = a[j+1];
a[j+1] = buf;
}
}
}
二、选择排序 O(n^2)
void SelectSort(SqList *L)
{
int i, j, min;
for(i = 1; j < L->len; i++)
{
min = i;
for(j = i + 1; j < L->len; j++)
{
if(L->r[j] < L->r[min])
min = j;
}
if(i!=min)
swap(L, i, min);
}
}
选定一个数,在剩余的序列中选择最小的数与之交换。
三、直接插入排序 O(n^2)
默认前面的序列已经排好序,从后面的序列中依次拿取元素放在前面的序列中合适的位置。 从后面序列拿去的元素依次和前面序列的每一个比它大的元素交换(从后向前),直到遇到比它小的元素为止。
void InsetSort(SqList *L)
{
for(int i = 1; i < L->len; i++)
{
for(int j = i - 1; j >= 0; j--)
{
if(L->r[j] > L->r[j+1])
swap(L, j, j+1);
}
}
}
四、希尔排序 O(n^3/2)
从数组首地址开始,每隔gap个元素选择一个元素组成子序列,子序列还是在原数组的相应位置上,对子序列使用插入排序算法排好序;再缩小gap,重复上述操作,直到gap=1,结束排序。
void ShellSort(SqList *L)
{
for(int gap = L->len / 2; gap >=1; gap/=2) //选择gap
{
for(int i = 0; i < gap; i++) //从第一个元素开始,每隔gap个元素,取一个元素,组成第一个子序列;从第二个元素开始,每隔gap个元素,取一个元素,组成第二个子序列;………………
{
for(int j = i - gap; j >= 0; j-=gap)
{
if(L->r[j] > L->r[j + gap])
swap(L, j, j + gap);
}
}
}
}
希尔排序是跳跃式的取值,相同的两个值可能分不到同一个子序列,导致排序后,顺序和之前不一样,因此,希尔排序不是稳定的排序算法。
五、堆排序
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了
void HeapSort(SqList *L)
{
for(int i = L->len / 2 - 1; i >= 0; i--) //从最后一个非叶子节点开始,构建堆
{
HeapAdjust(L, i, l->len);
}
for(int j = l->len - 1; j >= 0; j--)
{
swap(L, 0, j);
HeapAdjust(L, 0, j); //到j的意思是把根节点依次取出
}
}
void HeapAdjust(SqList *L, int p, int len)
{
int child = 2*p+1; //child为p节点的孩子节点
int temp = L->r[p];
while(child < len)
{
if(child + 1 < len && L->r[child] < L->r[child + 1]) //这个if是比较左右孩子节点的大小,并把大值节点的下标赋值给child
child++;
if(temp < L->r[child])
{ //为了调整堆的时候重用代码,此处不进行直接交换,而是一直迭代到不需要调整,再把根节点的值赋值给这个节点
L->r[p] = L->r[child];
p = child;
child = 2*p + 1;
}
else
break;
}
L->r[p] = temp;
}
堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。
六、归并排序
采用经典的分治(divide-and-conquer)策略将问题分成一些小的问题然后递归求解,即分而治之。
void MergeSort(int arr[], int low, int high)
{
if(low < high)
{
int mid = (low + high) / 2;
MergeSort(arr, low, mid); //high会变化
MergeSort(arr, mid + 1, high);
Merge(arr,low,mid,high);
}
}
void Merge(int arr[], int low, int mid, int high)
{
//low为第1有序区的第1个元素,i指向第1个元素, mid为第1有序区的最后1个元素
int i = low, j = mid + 1, k = 0; //mid+1为第2有序区第1个元素,j指向第1个元素
int *temp=new(nothrow) int[high-low+1]; //temp数组暂存合并的有序序列
if(!temp) //内存分配失败
{
cout<<"error";
return;
}
while(i <= mid && j <= high)
{
if(arr[i] <= arr[j]) //较小的先存入temp中
temp[k++] = arr[i++];
else
temp[k++] = arr[j++];
}
while(i <= mid) //若比较完之后,第一个有序区仍有剩余,则直接复制到t数组中
temp[k++] = arr[i++];
while(j <= high) //同上
temp[k++] = arr[j++];
for(i = low,k = 0;i <= high;i++,k++)//将排好序的存回arr中low到high这区间
arr[i] = temp[k];
delete []temp;//删除指针,由于指向的是数组,必须用delete []
}
非递归实现:
void MergeSort2(int arr[],int n) //n代表数组中元素个数,即数组最大下标是n-1
{
int size=1,low,mid,high;
while(size<=n-1)
{
low=0;
while(low+size<=n-1)
{
mid=low+size-1;
high=mid+size;
if(high>n-1) //第二个序列个数不足size
high=n-1;
Merge(arr,low,mid,high); //调用归并子函数
low=high+1; //下一次归并时第一关序列的下界
}
size*=2; //范围扩大一倍
}
}
时间复杂度为O(nlogn),空间复杂度为 O(n),归并排序比较占用内存,但效率高且稳定。
七、快速排序
快速排序:将数组分成两部分,其中一部分的任意值都比另一部分小;重复上述过程,直至排序完成。和冒泡排序一样,属于交换排序类,不同的是快排是跳跃式交换,效率更高,同时也是不稳定的排序算法。
void QuickSort(SqList *L, int low, int high)
{
int temp;
while(low < high)
{
temp = Partition(L, low, high);
QuickSort(L, low, temp - 1);
QuickSort(L, temp + 1, high);
}
}
int Partition(SqList *L, int low, int high)
{
int key = L->r[low]; //选择第一个数作为基准,左边的都比右边的小,返回基准的下标
while(low < high)
{
while(low < high && key < L->r[high])
high--;
swap(L, low, high);
while(low < high && l->r[low] < key)
low++;
swap(L, low, high);
}
return low;
}
最优情况下,基准值在序列中间位置,快排时间复杂度O(nlogn);最坏的情况下,待排序序列为逆序,每次划分只得到比上次少一个值的子序列,时间复杂度O(n^2)。平均来看,时间复杂度和空间复杂度均为O(nlogn)。
优化:
1、基准值的选取很重要,尽量选择位于的中间值,三数取中法:先选择low、mid和high三个数排序,将中间值作为基准。
2、优化不必要的交换
int Partition(SqList *L, int low, int high)
{
int key = L->r[low]; //选择第一个数作为基准,左边的都比右边的小,返回基准的下标
int temp = key;
while(low < high)
{
while(low < high && key <= L->r[high])
high--;
//swap(L, low, high);
L->r[low] = L->r[high];
while(low < high && L->r[low] <= key)
low++;
//swap(L, low, high);
L->r[high] = L->r[low];
}
L->r[low] = temp;
return low;
}
3、优化递归
总结: