内部排序全览

排序方法的稳定性:如果两个相同的关键字在排序前后顺序不变,认为是稳定的排序方法,反之,则是不稳定的排序方法。

两类排序方法:
1.内部排序:待排序的记录放在计算机的随机存储器中进行的排序
2.外部排序:待排序的记录数量很大,以致内存不能一次性容纳所有的记录,在排序的过程中需要访问外存

内部排序方法五大类:
插入排序,交换排序,选择排序,归并排序,计数排序

按照工作量来区分:
1.简单的排序方法 : 时间复杂度O(n*n)
2.先进的排序方法 :            O(n * log n)
3.基数排序       :           O(d * n)

排序过程中的两个基本的操作:
    1.比较两个关键字的大小   2.将记录从一个位置移到另一个位置

待排序的记录三种存储方式:
1.顺序存储:排序过程必然会有记录的移动
2.静态链表:进行排序时,就不需要移动记录,仅需修改指针--------》链表排序
3.待排序的记录存放在连续的存储单元,同时另设一个指示各个记录存储位置的地址向量:在排序时,不需要移动记录,只需要修改地址向量中地址--------》地址排序

    预设待排序记录的数据结构:
#define MAXSIZE 20
typedef int KeyType;
typedef struct{
 KeyType key;
 InfoType info;
}RedType;
typedef struct{
 RedType r[MAXSIZE+1]; //r[0]作为sentinel使用
` int  lenght;
}SqList;

1。插入排序
将一个新的记录插入到已经排序好的有序表中,从而得到一个新的长度增1的有序表。
在有序表的基础上插入。

1.1 直接插入排序
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[i];
   for(j = i-1; LT(L->r[i].key,L->r[j],key); --j)
    L->r[j+1] = L->r[j];
`   L->r[j + 1] = L->r[0];
  }
 }
}
时间复杂度分析:最坏的情况下,每次待排序的记录都需要放置在表头,每次需要比较 i + 1 次;其中的1是if中的比较,所以最坏情形下,n*n/4,时间复杂度为O(n*n)。
如果两个记录key相同,并不发生移动,所以为稳定的排序方法。

排序方法无非就是两个方面进行改进:1.减少比较次数 2.减少记录移动次数

1.2 折半插入排序 Binary Insertion Sort:
通过对有序表的二分查找,减少比较次数,较快地查找到插入点;不能减少移动次数
    折半查找得到的high是结点插入点的前一个位置。这样做的原因是有利于链表的插入操作。
void  BInserSort(SqList *L){
 for(i = 2; i < L->length; ++i){
  low = 1; high = i-1;
  L->r[0] = L->r[i];
  while(low <= high){
   mide = (low + high)/2;
                 if(LT(L->r[0].key, L->r[mid]).key) high = mid - 1;
   else low = mid + 1;
  }
  
  for(j = i-1; j >= high; --j) L->r[j+1] = L->r[j];
          L->r[high + 1] = L->r[0];
 }
 
}

1.3 二路插入排序
在折半插入查找的基础上进行改进,试图减少记录移动的次数。
需要设置一个预设与L同样类型和大小的数组d。
1.d[1] = L->r[1];作为排序的中间点,两个指针first  final都指向它
2.从第二个记录开始,与d[1]比较,如果大于或等于插入在d[1]之后的有序表
                                如果小于插入数组d[MAXSIZE]之前的有序表
3.first和final始终直线记录中最小和最大的

分析 : 如果L->r[1]是最大或者最小的那个记录意味着这个算法退化为简单的二分查找了

1.4 表插入排序

#define SIZE 100    //
typedef struct{
 RcdType rc;
 int next;
}SLNode;            //
typedef struct{
 SLNode r[SIZE];
 int length;
}SLinkListType;

其中头结点r[0].rc.key = MAXINT; next指向第一个结点,最后一个结点指向它,形成一个循环链表。
表插入排序依然是将一个记录插入到已经排序好的顺序表中,不同之处仅是以修改2n次指针值代替移动记录。
排序过程中进行关键字的比较次数依然不变,时间复杂度依然为O(n*n)。
且由于没有移动记录,所得到的只是一个有序链表,依然不能进行折半一样的对顺序表的查找。
因此还要根据真正进行调整,即移动记录。

修改游标函数:
void LSort(SLinkListType *L){
 L->r[0].rc.key = MAXINT; L->[0].next = 0;
   L->r[1].next = 0;

 for(i = 2; i< L->length; ++i){
  pre = 0;
  for(j = L->r[pre].next; j< i && LQ(L->r[j].rc.key,L->r[i].rc.key);j = L->r[j].next){
   pre = j;
  }
  L->r[j].next = i;
  L->r[i].next = j;   //类似于循环链表的插入,每次修改前后两个指针
 }
}
调整函数:

void Arrange(SLinkListType *L){
 p = L->r[0].next;                     //用来指示当前要进行调整的记录
 for(i = 0; i < L->length; ++i){
  while(p < i) p = L->r[p].next;
  q = L->r[p].next;               //用来指示下一个要进行调整的记录

  if(p != i){
   swap(L->r[i],L->r[p]);
   L->r[i].next = p;
  }
  p = q;
 }
}

1.5 希尔排序 shell‘s sort
又称缩小增量排序 diminishing increment sort。
基本思想:
如果待排序的序列式基本上有序的话,那么直接插入排序的效率是极高的。
     可以将待排序序列分成若干个子序列分别进行直接插入排序,这样完成后序列基本有序。
     在此基础上,对整个序列进行一次直接插入排序。

void ShellSort(SqList *L,int dlta[], int t){
 for(i = 0; i < t; ++i){
  ShellInsert(L,dlta[i]);
 }
}


//对直接插入排序的修改
void ShellInsert(SqList *L,int dk){
 for(i = dk +1; i < L->length; ++i){
  if(LT(L->r[i].key,L->r[i-dk].key)){
   L->r[0] = L->r[i];
   for(j = i-dk; j >0 && LT(L->r[0],L->r[j]);j -= dk; ){
    L->r[j +dk] = L->[j];
   }
   L->r[j + dk] = L->r[0];
  }
 }
}

2.交换排序

2.1 起泡排序bubble sort
// L->r[0] not be used
void  BubbleSort(SqList *L){
 for(i = 1; i < L->length; ++i){
  flag = FALSE;
  for(j = 1; j <= L->length - i; ++j)
   if( LT(L->r[i +1].key, L->r[i].key)){
    flag = TRUE;
    swap(L->r[i +1],L->r[i]);
   }
  if(!flag) break;
 }
}

时间复杂度:O(n*n)
分析:最坏的情况下,每次都有交换,从length-1 到1,即1+2+。。。+(n-1)=(n-1)*n/2

2.2 快速排序(Quick Sort)
基本思想:
通过一趟排序将待排序记录分割为独立的两部分,以枢轴为界,其中一部分的记录关键字都比枢轴小,另一部分的记录关键字都比枢轴大。每次排序都能讲枢轴放置在确定的位置。
枢轴常常使用第一个记录。

int Partition(SqList &L,int low ,int high){
 L->r[0] = L->r[low];
 pivotkey = L.r[low].key;

 while(low < high){
  while(low < high && L->r[high].key >= pivotkey) --high;
  L->r[low] = L->r[high];

  while(low < high && L->r[low].key < pivotkey) ++low;
  L->r[high] = L->r[low];
 }

 L->r[low] = L->r[0];
 return low;
}

void QSort(SqList *L,int low ,int high){
 if(low < high){
  pivotloc = Partition(L,low,high);
  QSort(L,low,pivotlox - 1);
  QSort(L,pivotlox + 1,high);
 }
}

void QuickSort(SqList *L){
 QSort(L,1,L->length);
}

就平均时间而言,快排是目前被认为的最好的一种内部排序方法,时间复杂度O(n*log(n))

3.选择排序Selection Sort

基本思想:每一趟在n-i+1个记录中选取关键字最小的记录作为有序序列中的第i个记录。

3.1 简单选择排序 simple selection sort

 void SelectSort(SqList *L){
 for(int i = 1; i < L.length; ++i){
  j = SelectMin(L,i);
  if(i != j) swap(L->r[i],L->r[j]);
 }
}

3.2 树形选择排序
Tree Selection Sort or Tournament Sort 锦标赛思想
辅助存储过多的弊端,使用堆排序进行弥补

3.3 堆排序heap sort
堆在逻辑上上一个完全二叉树。  主要用来形成和处理优先级队列,其中只需要返回最大或最小记录,和有限的插入操作。
对于堆的要求,只要堆中子结点中关键字都小于或大于父节点。

堆排序的基本思想:
重点是如何实现堆的调整,在特别是加入新的结点后,将新结点与首结点交换,需要检查堆结构是否被破坏,需要进行调整。
      调整的原理很简单,如果当前结点关键字大于子结点,不符合堆结构要求,结点下移直到一个合适的位置。

typedef SqList HeapType;

//从结点s到结点m之间的调整,如果结点s的左右孩子存在(即j<m),找到左右子树中较大的记录
  如果这个记录比结点s的记录小,则需要调整(如果要形成非递减序列,那么子结点就应该存放关键字较大的记录),否则退出循环。
  所谓的调整,就是自s结点不停向下调整,总是将较大的记录下移,较小的记录上移。

void HeapAdjust(HeapType *H, int s, int m){
 rc = H->r[s];
   for(j = 2 * s; j <= m; j *= 2){
  if(j < m && LT(H->r[j].key, H->r[j+1].key))  ++j;
  if(!LT(rc.key,H->r[j].key)) break;
  H->r[j] = rc;
  }
}
//堆排序
void HeapSort(heapType *H){
 //自所有的非叶子结点开始进行调整,建成大顶堆
 for(i = H->length /2; i > 0; --i){
  HeapAdjust(H,i,H->lenght);
 }

  //在原有的堆的基础上,每一次将最后结点与首节点交换,逐一调整,得到大根堆
   for(i = H->length; i >1; --i){
  swap(L->r[1],L->r[i]);
  HeapAdjust(H,1,i-1);
 }
}


4.归并排序merging sort
  顾名思义,是将两个或者更多的有序表归并为一个有序表。
            无论是顺序存储还是链式存储,都是在O(m + n)的时间复杂度上完成的。m、n分别表示两个子有序表的长度。

4.1 2—路归并排序
将一维数组前后相邻的两个有序序列归并为一个有序序列。

void Merge(RcdType SR[], RcdType (*TR)[], int i ,int m,int n){
 for(j = m + 1, k = i; i <= m && j <= n; ++k){
  if( LQ(SR->r[i].key,SR->r[j])) TR->r[k] = SR->r[i++];
       else TR->r[k] = SR->r[j++];
 }
 if(i <= m) TR[k .. n] = SR[i .. m];
 if(j <= n) TR[k .. n] = TR[j .. n];
}

void MSort(RcdType SR[], RcdType (*TR)[],int s,int t){
 if(s == t) TR->r[s] = SR[s];
 else{
  m = (s + t)/2;
  MSort(SR,TR,s,m);
  MSort(SR,TR,m + 1,t);
  Merge(SR,TR,s,m,t);
 }
}

void MergeSort(Sqlist *L){
 MSort(L.r,L.r,1,L->length);
}

5.基数排序   Radix Sort
基数排序不需要进行关键字间的排序。
基数排序是一种借助多关键字排序的思想对单逻辑关键字进行排序的方法。

多关键字的排序:
最高位优先排序法 most significant digit first MSD: 从主关键字到次关键字依次完成的排序
需要将序列逐层分割成若干个子序列,然后对各个子序列分别进行排序。

最低位优先排序法 least significant digit first LSD: 从主关键字到次关键字依次完成的排序
不必分成子序列,对每一个关键字都是整个序列参加排序。 可以通过若干次的分配和收集来实现排序。

链式基数排序:
基数排序是借助于“分配”和“收集”两种操作队单逻辑关键字进行排序的一种内部排序方法。
把单逻辑关键字看做是由多个关键字复合而成。如数字由多个位组成,字符串由多个字符组成。

以数字排序为例,关键字为单位数字0~9,用单个链表存放数字,分配时用10个队列存放,回收时归为链表中。
用两个指针分别指向队列的首尾  f[i]  e[i] 分别指向第i个队列的首尾指针。

#define MAX_NUM_OF_KEY 8
#define RADIX 10         ----->
#define MAX_SPACE 10000

typedef struct{
 KeysType keys[MAX_NUM_OF_KEY];
 InfoType info;
 int next;
}SLCell;                  //

typedef struct{
 SLCell r[MAX_SPACE];
 int keynum;
 int recnum;
}SLList;                  //

typedef int ArrType[RADIX];

void RadixSort(SLList *L){
 for(i = 0; i < L->recnum; ++i) L->r[i].next = i+1;
 L->r[recnum].next = 0;

 for(i = 0;i < L->keynum; ++ i){
  Distribute(L->r,i,f,e);
  Collect(L->r,i,f,e);
 }
}//RadixSort

void Distribute(SLCell *r,int i,ArrType *f,ArrType *e){
 for(j = 0; j < radix; ++j) f[j] = 0;
 for(p = r[0].next; p; p = r[p].next){
  j = ord(r[p].keys[i]);        //将记录中第i个关键字映射到0~radix-1
  if(!f[j]) f[j] = p;           //如果队列为空,p插入队头
  else r[e[j]].next = p;        //如果不空,p插入队尾
  e[j] = p;                    //将p所指的结点插入到第j个子表中
 }
}

void Collect(SLCell *r,int i,ArrType f,ArrType e){
 for(j = 0;!f[j];j = succ(j));
 r[0].next = f[j];   t = e[j];
 
 while(j<radix){ 
  for(j = succ(j);j<radix-1 && !f[j];j = succ(j));
  if(f[j])  { r[t].next = f[j] ; t = e[j];}
 }
 r[t].next = 0;
}

基数排序的时间复杂度为:O(d*(n + rd)),稳定的排序方法。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值