内部排序
- 内部排序分为:插入排序,交换排序,选择排序,归并排序,基数排序
1.插入排序
1.1 直接插入排序
这个是比较好理解,但操作起来比较麻烦,不建议用这种方法。
假设我们有这样一组数:49 38 65 97 76 13 27 49
我们从第一个49开始排序,(49)。
一次看下一个数38,因为38比49小,所以排在49前面,(38 ,49)一次按这个规律排序。
(38 49 65)
(38 49 65 97)
(38 49 65 76 97)
(13 38 49 65 76 97)
(13 27 38 49 65 76 97)
(13 27 38 49 49 65 76 97)(注:原数列中最后一个49与第一个49相同,排序时原数列最后一个49应排在原数列第一个49的后面。)
算法如下:
Void InsertSort(SqList &L) {
for (i= 2; i<L.Length; ++i )
if LT(L.r[i].key,L.r[i-1].key){
L.r[0] =L. r [1];
L. r[i] = L.r[i-1];
for (j =i-2;LT(L.r[0].key,L.r[i].key);--j)
L.r[j+1] =L. r[i];
L.r[j+1]=L.r[0];
}
}InsertSort
1.2 折半插入排序
折半插入排序是在有序表中进行插入。 如图所示,1 2 3 4为有序表,5 6 7 8 9 10 11为待排序的子表。有序表第一个数low,最后一个为high,中间数mid。子表数与mid比较,小于mid在mid前面进行排序,大于mid在mid后面排序。
算法:
void BInsertSort(SqList & L ) {
for (i=2;i<= L .length; ++i) {
L. r[0] = L. r [i];
low=1;high=i-1;
while (low<= high) {
m =(low + high)/2;
ifLTL.r[0].key,L.r[m].key)high=m-1;
else low = m+1;
}// while
for( j = i-1; j>= high +1;-- j) L.r[j+1] = L. r[j];
L.r[high+1]= L.r[0];
}// for
}// BInsertSort
1.3 2-路插入排序(不是很常用)
2-路插入排序是在折半插入排序的基础上的发展。其目的是减少排序过程中记录移动的次数,但为此需n个记录的辅助空间。
具体做法是:另设一个和L.r同类型的数组d,首先将L.r[1]赋值给d[1],将d[1]看成是排好序的序列中处于中间位置的记录,然后从L.r中第2个记录起依次插入到d1l之前或之后的有序序列中。先将待排序记录的关键字和d[1]的关键字比较,若L.r[i].key<d[1] .key,则将L.r[i]插入到d[1]之前的有序表中。反之,将L.r[i]插入到d[1]之后的有序表中。
在实现算法时,将d看成一个循环向量,并设两个指针first和final分别指示排序过程中得到的有序序列中的第一个记录和最后一个记录在d中的位置。
1.4 希尔排序(重要)
原理:先将整个待排序的记录分割成若干子序列分别进行“直接插入排序”接插入排序。同一颜色进行排序,间隔最好奇次。
算法:
void ShellInsert(SqList&Lintdk){(dk为跨度,r[0]是暂存单元j<=0,插入位置已找到)
for(i=dk+1;i<=L.length;++i){(控制要排序子表个数)
ifLT(L.r[i].key,L.r[i-dk].key) (L.ri为待排序记录且小于)
L.r[0] = L.r[i]; (将L.r[i]暂存到L.r[0])
for(j=i-dk;j>0 &<(L.r[0].key,L.r[i].key);j - = dk)
L.r[j+dk]=L.r[i]; (将大于插入值的记录向后移动)
L.r[j+dk]=L.r[0]; (插入)
}
} ShellInsert
void ShellSort( SqList &L, int dita[], int t )
{ (dita[0... t-1] 存放跨度的数组)
for ( k = 0 ; k<t; ++ k)
ShellInsert(L, dita[k]); (由dita[k]得到跨度)
}ShellSort
2.交换排序
交换排序分为:冒泡排序,快速排序(非常快)
2.1 冒泡排序
大家学C语言的时候都学过冒泡排序,这个和它是一样的。简单回忆一下如何冒泡排序吧! 一组数中,把最大的数放在最后面,不断循环,直到排序完成。(类似于鱼吐泡泡)举一个列子,
49 65 23 98 34 78用冒泡排序
49 65 23 34 78 98
49 23 34 65 78 98
23 34 49 65 78 98。 这样冒泡就完成啦!
2.2 快速排序
快速排序的基本思想是把当前待排序的记录,存放到整个表排好序后,它应当在的最终位置上。将原来的待排序表分割成两部分,其中一部分表中的关键字均比另一部分表中的关键字小。然后,分别对两部分表用同样的的方式进行排序,直到整个表排好序。
算法:
int Partition( SqList &L, int low, int high){
pivotkey =L.r[low].key; //(pivotkey是辅助空间)
while (low< high ) {
while(low <high && L.r[high].key>=piotkey)--high;
L.r[low] <-> L.r[high];
while (low<high&&L.r[low].key<=pivotkey) ++low;
L.r[low] <->L.r[high];
}
L.r[low]= pivotkey;
return low;
}// Partition
Void QSort (SqList &L, int low, int high) {
if ( low< high) {
picotloc =Partition(L,low,high);
QSort(L,low,pivotloc-1);
QSort(L, pivotloc+1 high);
}//QSort
Void QuickSort(SqList &L){
Qsort(L,1,L.length);
}//QuickSort
3.选择排序
选择排序分为:简单选择排序和堆排序。简单选择排序经过改进有了堆排序。
3.1 简单选择排序
选择排序是每一趟在n -i+1(i= 1,2,3...n-1) 个记录中选择关键字最小的记录化作为有序序列中第i个记录。其中最简单的是简单选择排序。
算法:
void SelectSort(SqList &L ) {
for (i =1; i<L.length; ++i) {
j =SelectMinkey(L,i );
if (i ! = j) L.r[i] <-> L.r[i];
}// SelectSort
3.2 堆排序
堆:又称堆树,它是一棵特殊的完全二叉树,其中堆排序分为大堆和小堆。
小堆:树中每个结点关键字必须小于左右孩子关键字(叶子结点除外)。
大堆:树中每个结点关键字必须大于左右孩子关键字(叶子结点除外)。
例如给定关键字集合(49 38 65 97 76 13 27 49)按顺序输入构成一棵完全二叉树。
算法:
type SqList HeapType; //堆采用顺序表存储表示
void HeapAdjust (HeapType &H, int s,int m) {
rc =H.r[s] ;
for (j = 2*s; j<=m ;j* =2) {
//沿key较大的孩子结点向下筛选
if (j<m && LT(H.r[i].key,H.r[j+1].key)) ++j;
//j为key较大的记录的下标
if(!LT( rc.key, H.r[j].key)) break;
//rc应插入在位置s上
H.r[s]=H.r[i];s=j;
}
H.r[s] = rc;
}// HeapAdjust
void HeapSort (HeapType & H ) {
for (i=H.length/2; i>0;--i)
HeapAdjust(H, i,H.length);
for (i= H.length; i>1 ; --i ){
H.r[1] ← H.r[i] ;
printf(H.r[i]);
HeapAdjust( H, 1, i-1);
} //HeapSort
4.归并排序
归并排序是把两个或两个以上的有序表合并成一个新的有序表。把含有N个记录的无序表当成N个有序的子表,每个子表的的长度为1,然后,利用两两归并,得到n/2个长度为2或1的有序子表。再两两归并直到得到长度为N的一个有序表。
例如: 对49 38 65 97 76 13 27 进行归并排序。
算法;
void Merge(RcdType SR,RcdType &TR, inti, int m, int n){(J为第二个表的下限初值)
for(j=m+1,k=i; i<= m && j<= n; ++ k){
if LQ(SR[i].key,SR[i].key)
TR[K] = SR[i++];
else TR[k]=SR[j++] } (把第J个值送后再加1)
if (i<=m)TR[k...n]=SR [i...m];(第二个表空)
if(j<=n)TR[k...n]=SR[j..n]; (第一个表空)
}// Merge
void Msort(RcdType SR[], RcdType&TR1[],int s, int t) /*int s为排序表的下限, int t为排序表的上限*/
{
if(s==t) TR1[s]=SR[s];
else {
m=(s+t)/2; (将SR[s...t]平分为SR[s.….m]和 SR[m+1...t])
Msort(SR,TR1,s,m); (递归地将SR[s…m]归并为有序的TR1[s...m])
Msort(SR,TR2, m+1,t); (递归地将SR[m+1...t]归并为有序的TR2[m+1..t])
Merge(TR2,TR1,s,m,t); (将TR2[m+1..t]]归并为TR1[s..t])
}// Msort
void MergeSort( SQList & L){
Msort (L.r, L.r1, 1,L.length);
}// MergeSort
5.基数排序
排序的一种内排序方法。根据在实际工程中使用的关键字大都是由数字(或字母)组成的,现在,以数字为例来说明这种排序的过程我们知道数字关键字都是由0到9一个数字组成,那么,在内存设立编号为0-9的十个桶。对待排序的记录从其关键字的最低位到最高位作如下处理:把每个记录存放(分配)到内存中桶号与该记录关键字当前数位上数字相同的桶中。经过处理(收集)得到一次排序结果。
下面以顺序存储为例看一看实际过程:
由上面分析可知基数排序若用顺序存储,每个桶都要分配 n个单元总的空间量应为10*n。作但实际上根本用不了那么多,因而造成空间的极大浪费。所以,基数排序一般都采用链表存储。链表存储的排序原理与顺序存储相同,不再论述,大家可以自己看。
各种内部排序方法的比较
- 平均时间性能:快速排序→归并排序→堆排序→简单排序
- 简单程度:直接插入排序最简单。常与其他方法结合使用。
- 稳定性:快速排序、堆排序、希尔排序都是不稳定的。
- 基数排序最适用于n值很大而关键字车较小的序列。
- 我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿。