数据结构-王道-排序
排序
直接插入排序
从上面的插入排序思想中,不难得到一种简单直接的插入排序算法。假设待排序表在某次过程中属于这种情况。
|有序序列\(L[1\ldots i-1]\)|L(i)|无序序列\(L[i+1\ldots n]\)|
|:-|:-|
为了实现将元素\(L(i)\)插入到已有序的子序列\(L[1\ldots i-1]\)中,我们需要执行以下操作(为了避免混淆,下面用\(L[]\)表示一个表,而用\(L()\)表示一个元素):
- 查找出\(L(i)\)在\(L[i+1\ldots n]\)中的插入位置k。
- 将\(L[k\ldots i-1]\)中所有元素全部后移一个位置。
- 将\(L(i)\)赋值到\(L(k)\)
void InserSort(int A[],int n)
{
int i,j;
for(i=2;i<=n;i++)
{
if(A[i]<A[i-1])
{
A[0]=A[i];
for(j=i-1;A[0]<A[j];j--)
A[j+1]=A[j];
A[j+1]=A[0];
}
}
}
折半插入排序
从前面的直接插入排序算法中,不难看出每趟插入的过程,都进行了两项工作:
- 从前面的子表中查找出待插入元素应该被插入的位置。
- 给插入位置腾出空间,将待插入元素复制到表中的插入位置。
注意到该算法中,总是边比较边移动元素,下面将比较和移动操作分离开,即先折半查找出元素的待插入位置,然后再同意的移动待插入位置之后的元素。
void InserSort(int A[],int n)
{
int i,j,low,high,mid;
for(i=2;i<=n;i++)
{
A[0]=A[i];
low=1,high=i-1;
while(low<=high)
{
mid=(low+high)/2;
if(A[mid]>A[0])
high=mid-1;
else
low=mid+1;
}
for(j=i-1;j>=high+1;j--)
A[j+1]=A[j];
A[high+1]=A[0];
}
}
折半插入排序
从前面的代码原理中不难看出,直接插入排序适用于基本有序的排序表和数据量不大的排序表。1959年\(D.L.Shell\)提出了希尔排序,又称为缩小增量排序。
希尔排序的基本思想是:先将待排序表分割为若干个形如\(L[i,i+d,i+2d,\ldots,i+kd]\)的特殊子表,分别进行直接插入排序。希尔排序的排序过程如下:
先取一个小于n的步长d1,把表中全部记录分为\(d_1\),所有距离为\(d_1\)的倍数的记录放在同一个组中,在各组中进行直接插入排序;然后取第二个步长\(d_2\leq d_1\),重复上述过程,直到所取到的\(d_t=1\),即所有记录已放在同一组中,再进行直接插入排序,由于此时已经具有较好的局部有序性,故可以很快的得到结果。到目前为止,尚未求得一个最好的增量序列,希尔提出的方法是\(d_1=\frac n 2\),\(d_{i+1}= \frac {d_i} 2\),并且最后一个增量等于1。
void SheelSort(int A[],int n)
{
for(int dk=n/2;dk>=1;dk=dk/2)
for(int i=dk+1;i<=n;i++)
if(A[i]<A[i-dk])
{
A[0]=A[i];
for(int j=i-dk;j>0&&A[0];j=j-dk)
A[j+dk]=A[j];
A[j+dk]=A[0];
}
}
冒泡排序
冒泡排序的算法思想是:假设待排序表长为n,从后往前(或者从前向后)两两比较相邻元素的值,若为逆序(即\(A[i-1]>A[i]\)),则交换他们,知道序列比较完。我们称之为一趟冒泡,结果将最小的元素交换到待排序的第一个位置(关键字最小的元素如气泡一般逐渐向上“漂浮”直至“水面”,这就是冒泡排序名字的由来)。下一趟冒泡的时候,前一趟确定的最小元素不再参加比较,待排序列减少一个元素,每趟冒泡的结果把序列中的最小元素放到了序列的最终位置。
void BuuleSort(int A[],int n)
{
for(int i=0;i<n-1;i++)
{
bool flag=false;
for(int j=n-1;j>i;j--)
if(A[j-1]>A[j])
{
swap(A[j-1],A[j]);
flag=true;
}
if(flag==false)
break;
}
}
快速排序
快速排序是对冒泡排序的一种改进,其基本思想是基于分治法的:在待排序表\(L[1\ldots n]\)中任取一个元素pivot作为基准,通过一趟排序将待排序表划分为独立的两部分\(L[1\ldots k-1]\)和\(L[k+1\ldots n]\)使得\(L[1\ldots k-1]\)中所有元素小于pivot,\(L[k+1\ldots n]\)中所有元素均大于或等于pivot,则pivot放在了其最终位置\(L(k)\)上,这个过程称为一趟快速排序。而后分别递归的对两个子表重复上述过程,直至每部分内只有一个元素或者为空为止,即所有元素放在了其最终位置之上。
首先假设划分算法已知,记为\(Partition()\),返回的是上述的k,注意到\(L(k)\)已经在最终的位置,所以可以先对表进行划分,而后对两个表调用同样的排序操作。因此可以递归的调用快速排序算法进行排序,具体的程序结构如下:
int Partition(int A[],int low,int high) // 传入 数组和 上下限
{
int pivot = A[low]; // 让pivot暂存传入的数组第一个值.
while(low<high) // 当low等于high的时候 才跳出去。
{
while(low<high&&A[high]>=pivot) // 当low小于high并且数组靠前的值
high--; //开始 减小high的值 知道不符合上述条件
A[low]=A[high]; // 较小的值 放到前面 那个空位上
while(low<high&&A[low]<=pivot) // 当low小于high并且数组靠前的值比较小
low++;
A[high]=A[low];
}
A[low]=pivot;
return low;
}
void QuickSort(int A[],int low,int high)
{
if(low<high)
{
int pivot = Partition(A,low,high);
QuickSort(A,low,pivot-1);
QuickSort(A,pivot+1,high);
}
}
简单选择排序
从上面选择排序的思想中可以很直观的得出简单选择排序算法的思想:假设排序表\(L[1\ldots n]\),第i趟排序即从\(L[i\ldots n]\)中选择关键字最小的元素与\(L(i)\)交换,每一趟排序可以确定 一个元素的最终位置,这样经过\(n-1\)趟排序就可以使整个排序表有序。
void SelectSort(int A[],int n)
{
for(int i=0;i<n-1;i++)
{
int Min=i;
for(int j=i+1;j<n;j++)
if(A[j]<A[Min])
Min=j;
if(Min!=i)
swap(A[i],A[Min]);
}
}
堆排序
堆排序是一种树形选择排序方法,它的特点是:在排序过程中,将\(L[1\ldots n]\)看做一棵完全二叉树的顺序存储结构,利用完全二叉树双亲结点和孩子结点之间的内在关系,在当前无序区中选择关键字最大。
堆的定义如下:n个关键字序列\(L[1\ldots n]\)称为堆,当且仅当该序列满足:\(L(i)\leq L(2i)\)且\(L(i)\leq L(2i+1)\)或\(L(i)\geq L(2i)\)且\(L(i)\geq L(2i+1)\)。
满足前者的称为小根堆(小顶堆),满足后者情况的堆称为大根堆(大顶堆)。显然,在大根堆中,最大元素存放在根节点中,且对其任一费根节点,它的值小于或者等于其双亲结点值。小根堆的定义刚好相反,根节点是最小元素。下图所示为一个大根堆。