1.基本排序概念
(1.排序:
就是将一个元素的任意序列重新排成一个按关键字有序的序列。
(2.稳定性:
如果待排序表中有两个元素Ri、Rj,其对应关键字为Ki=Kj,且排序前Ri在Rj之前,如果使用某一排序算法后,Ri仍在Rj前面,则称这个排序方法是稳定的,否则称之为不稳定排序。
(3.内部排序:
排序期间,元素全部存放在内存中的排序。
(4.外部排序:
排序期间,元素无法全部同时放在内存中,必须在排序过程中根据要求不断地在内、外存之间移动的排序。
2.排序分类
排序的基本分类如下图所示:
以上便是排序类型的一种分类,这也是常用的一种方式。其实,看到这张图的时候,大家心里对排序算法的知识结构已经很清晰了。那么接下来就让我进一步去了解一下排序算法中的几种常见内部排序算法吧。
注:今天的笔记主要是关于内部排序中的几种常见排序方法,并用C语言以整型数据为例实现各类排序算法。
3.内部排序
插入排序
3.1.1、直接插入排序
a.基本思想:每一趟将一个待排序的记录按其关键字的大小插入到前面已经排好序的子序列中的合适位置上,直到全部记录插入完成。
b.代码实现:
void InsertSort(int a[],int n)
{
int i,j,L;
for(i=1; i<n; ++i)
if(a[i]<a[i-1]){
L = a[i];
for(j=i; j>0; --j){
a[j] = a[j-1];
if(a[j-2]<L) break;
}
a[j-1] = L;
}
}
3.1.2、折半插入排序
a.基本思想:与直接插入排序类似,不同之处在于直接插入排序采用顺序查找发,而折半插入排序是采用折半查找法。
b.代码实现:
void HalfInsertSort(int a[],int n)
{
int i,j,low,high,mid,L;
for(i=1; i<n; ++i)
if(a[i]<a[i-1]){
L = a[i];
low=0,high=i-1;
while(low<=high){
mid = (low+high)/2;
if(L<a[mid]) high=mid-1;
else low=mid+1;
}
for(j=i;j>high+1;j--) a[j] = a[j-1];
a[j] = L;
}
}
3.1.3、希尔排序
a.基本思想:先将整个待排序的序列分为若干子序列分别进行直接插入排序,当整个序列中的元素“基本有序”时,再对全体元素进行依次直接插入排序,故又称增量缩小排序。
b.代码实现:
void ShellSort(int a[],int n)
{
int i,j,dk,L;
for(dk=n/2; dk>0; dk/=2)
for(i=dk; i<n; ++i)
if(a[i]<a[i-dk]){
L = a[i];
for(j=i;j>0&&a[j-dk]>L;j-=dk)
a[j] = a[j-dk];
a[j] = L;
}
}
交换排序
3.2.1、冒泡排序
a.基本思想:两两比较相邻元素值,若为逆序,则交换它们,直到序列比较完成,我们称其为一趟冒泡,再下一趟时,已经确定的元素就不再参与比较。
b.代码实现:
void BubbleSort(int a[],int n)
{
int i,j,flag;
for(i=0;i<n;++i){
flag = 1;
for(j=0;j<n-i-1;++j){
if(a[j]>a[j+1]){
a[j]+=a[j+1];
a[j+1]=a[j]-a[j+1];
a[j]-=a[j+1];
flag = 0;
}
}
/*已经排序结束*/
if(1==flag) break;
}
}
3.2.2、快速排序
a.基本思想:基于分治思想,通过一趟排序将待排序的元素分成两部分,其中一部分元素值均比基准元素值小。另一部分元素值比基准值大。那么基准值就排到最终位置上。再分别对上述两部分进行同样处理,直到整个序列有序。
b.代码实现:
void QuickSort(int a[],int low, int high)
{
int pov;
if(low<high){
pov = Partition(a,low,high);
QuickSort(a,low,pov-1);
QuickSort(a,pov+1,high);
}
}
int Partition(int a[],int low,int high)
{
int L = a[low];
/*首尾缩进方式*/
while(low<high){
while(low<high&&a[high]>=L) --high;
a[low] = a[high];
while(low<high&&a[low]<=L) ++low;
a[high] = a[low];
}
a[low]=L;
return low;
}
选择排序
3.3.1、简单选择排序
a.基本思想:每一趟在未排序的元素中找出关键字最小的元素作为有序子序列的末元素,下一趟再从有序子序列之后的后一个元素开始查找比较,直到整个序列有序。
b.代码实现:
void SelectSort(int a[],int n)
{
int i,j,min;
for(i=0; i<n-1; ++i){
min = i;
for(j=i+1; j< n; ++j)
if(a[j]<a[min]) min = j;
if(min!=i){
a[i] += a[min];
a[min] = a[i]-a[min];
a[i]-= a[min];
}
}
}
3.3.2、堆排序
a.基本思想:堆排序是一种树形选择排序,是对直接选择排序的一种改进。包括构建初始堆和利用堆排序两个过程,最终实现排序。具体介绍可参考其他资料。
演示说明:在排序的逻辑上我们采用树形结构,但是数据在内存中的表示我们可以使用其他形式来代替。在这里,借助满二叉树的特新,我们使用一维数组来存储数据,他们之间的树形为:父节点为2*k-1,左孩子是2*k+1,右孩子是2*k+2。逻辑与存储分析如下图。
b.代码实现:
void AdjustHeap(int a[],int start, int end)
{
int i,k,L,lc,rc;
for(i=end; i>start; --i){
k = (i-1)/2;
/*与父节点比较*/
if(a[i]<a[k]){
a[i]+=a[k];
a[k]=a[i]-a[k];
a[i]-=a[k];
/*交换后分别与两个孩子比较,防止调整过程中父节点值比孩子节点值大*/
if((lc=2*i+1)<=end&&a[i]>a[lc]){
a[i]+=a[lc];
a[lc]=a[i]-a[lc];
a[i]-=a[lc];
}
if((rc=2*i+2)<=end&&a[i]>a[rc]){
a[i]+=a[rc];
a[rc]=a[i]-a[rc];
a[i]-=a[rc];
}
}
}
/*将得出的最小元素放至本次排序序列的末尾,最终得到一个逆序的有序序列*/
L = a[start];
a[start] = a[end];
a[end] = L;
}
void HeapSort(int a[],int n)
{
int i;
for(i=0; i<n; ++i)
AdjustHeap(a,0,n-i-1);
}
归并排序
a.基本思想:将两个或两个以上的有序序列组合成一个新的有序序列,具体为从各个序列首元素开始,依次比较,满足要求的元素按顺序存入新的序列中,而该元素之前所在序列的对较对象后移一个,再与其他序列的比较对象元素进行比较,直到只剩下一个比较序列,然后把最终剩下的这个序列中还未比较的元素全部按顺序复制到目标归并序列的末尾,从而实现有序序列的归并。
演示说明:此处演示将一个数组分成两部分,target数组长度应和a数组长度一致,用于承接a数组两部分有序序列的归并结果。
b.代码实现:
void MergeSort(int a[], int div, int n,int target[])
{
/*将a数组中的两部分有序序列归并到target中*/
int i=0,j=div,k=0;
while(i<div&&j<n){
if(a[i]<a[j]){
target[k] = a[i];
++i;
}else{
target[k] = a[j];
++j;
}
++k;
}
/*将剩余元素按顺序复制到归并目标序列中去*/
if(i==div)
for(j;j<n;++j,++k)
target[k] = a[j];
else if(j==n)
for(i;i<div;++i,++k)
target[k] = a[i];
}
基数排序
a.基本思想:一种借助多关键字排序的思想对单逻辑关键字进行排序的方法。
由于该算法是对多个关键字进行”收集“和“分配”的过程,可结合玩扑克拍时按不同的规则整理排序来理解,所以此处就不予演示了。
后记:可根据对上述算法的理解,然后基于时间复杂度、空间复杂度、算法稳定性三个因素对其进行比较和理解。第一次写这东西,难免笔记中存在些许错误,欢迎大家指正。