ZqrFerrari
努力学习,开心生活

 

1. 排序的基本概念

  假定排序对象为若干记录组成的一个集合,每个记录包含若干个字段,选取其中一个或多个字段为排序码。我们暂时假设排序码的类型为整数类型。

  “正序”序列:待排序序列正好符合排序要求。

  “逆序”序列:把待排序序列逆转过来,正好符合排序要求。

  排序的稳定性:排序码相同的记录经过排序后相对次序保持不变,则这种排序方法称为是“稳定的”,否则是不稳定的。

2. 分类

  按排序中涉及的存储器不同

  1)内部排序是把待排数据元素全部调入内存中进行的排序。

  2)外部排序是因数量太大,把数据元素分批导入内存,排好序后再分批导出到磁盘和磁带外存介质上的排序方法。

  按照排序方法分类

  1)插入排序:直接插入、二分法插入、表插入、Shell排序

  2)选择排序:直接选择、堆排序

  3)交换排序:冒泡排序、快速排序

  4)分配排序:基数排序

  5)归并排序:二路归并排序

3、排序算法的评价

  1)时间复杂度:分析记录关键字的比较次数和记录的移动次数  (重要评价标准)
  2)空间复杂度:算法中使用的内存辅助空间
  3)排序的稳定性
  4)算法本身的复杂程度

 

一、选择排序与堆排序

1.直接选择排序

  思路比较简单:即依次从剩余记录中选取最小的

 

代码
void SelectSort(SeqList R)
{
   
int i,j,k;

   
for(i=1;i<n;i++)
   {                                
//做第i趟排序(1≤i≤n-1)
     k=i;
     
for(j=i+1;j<=n;j++)            //在当前无序区R[j..n]中选key最小的记录R[k]
      
    
if(R[j].key<R[k].key)
         k
=j;                       //k记下目前找到的最小关键字所在的位置
       
     
if(k!=i)
     {                             
//交换R[i]和R[k]
            R[0]=R[i];R[i]=R[k];R[k]=R[0];                 //R[0]作暂存单元
     }                               
   }                                
}                                

 

 

2.堆排序

  利用堆的思想,建立一个最大堆,把堆顶的元素(最大值)拿掉,再重新建堆,依次递归
代码
void heapSort(int data[], int len)    
{   
    heapify(data, len);        
    
int end = len - 1;       
    
while(end > 0)        
    {                                                            
//将根节点放到最后,因为根节点总是最大的            
        int tmp = data[end];            
        data[end] 
= data[0];            
        data[
0= tmp;                                            //由于根节点的值被修改了,需要对以该节点为根的树进行siftDown操作,该操作执行完后,能够保证其仍然是最大堆            
        siftDown(data, 0, end - 1);            
        end
--;        
    }    
}   

void heapify(int data[], int len)    
{        
    
int start = (len - 2/2;                                    //指向最后一个父亲节点       
    while(start >= 0)                                            //从下往上建立最大堆        
    {                
        siftDown(data, start, len 
- 1);            
        start
--;        
    }    
}                                                                
//该方法只是确保以start为根节点的树是一个最大堆,但是不会保证start + 1为根节点的树   

void siftDown(int data[], int start, int end)    
{        
    
int root = start;        
    
while(root * 2 + 1 <= end)                                    //若具有儿子节点        
    {           
        
int child = root * 2 + 1;                                //判断是否具有右儿子节点,并指向最大的儿子节点           
        if(child < end && data[child] < data[child + 1])            
        {                
            child 
= child + 1;           
        }                                                        
//将较大的作为父亲节点            
        if(data[child] > data[root])            
        {               
            
int tmp = data[root];                
            data[root] 
= data[child];               
            data[child] 
= tmp;                                    //循环下去,确保被更新的儿子节点是其子树的最大值                
            root = child;            
        }           
        
else            
        {               
            
break;           
        }       
    }    
}

 

二、冒泡排序

  依次比较相邻的两个数,将小数放在前面,大数放在后面。即首先比较第1个和第2个数,将小数放前,大数放后。然后比较第2个数和第3个数,将小数放前,大数放后,如此继续,直至比较最后两个数,将小数放前,大数放后。重复以上过程,仍从第一对数开始比较(因为可能由于第2个数和第3个数的交换,使得第1个数不再小于第2个数),将小数放前,大数放后,一直比较到最大数前的一对相邻数,将小数放前,大数放后,第二趟结束,在倒数第二个数中得到一个新的最大数。如此下去,直至最终完成排序。

代码1
void bubbleSort(int data[], int len)    
{        
    
for(int i = 1; i <= len - 1; i++)                //每交换一轮,均会有一个最大的冒泡到最右边.        
    {            
        
for(int j = 0; j < len - 1; j++)            
        {                
            
if(data[j] > data[j + 1])                
            {                    
                
int tmp = data[j];                    
                data[j] 
= data[j + 1];                    
                data[j 
+ 1= tmp;               
            }           
        }      
    }   
}

 

代码2
void bubbleSort(int data[], int len)    
{                    
//利用标志位来控制循环早点结束,若遍历一次交换次数为0说明是有序的,可以提前结束.        
    bool swapped = true;        
    
while(swapped)        
    {            
        swapped 
= false;            
        
for(int j = 0; j < len - 1; j++)            
        {                
            
if(data[j] > data[j + 1])                
            {                   
                
int tmp = data[j];                    
                data[j] 
= data[j + 1];                    
                data[j 
+ 1= tmp;                    
                swapped 
= true;               
            }           
        }       
    }   
}

 

三、快速排序

   目前比较流行,也是公认较好的排序算法,其利用了分治的算法思想,首先选取一个pivot,将小于pivot的记录放在左边,其余放在右边,并不断递归对左右两边的序列进行快速排序。

代码
void quickSort(int data[], int left, int right)    
{        
    
if(left >= right)
        
return;       

    
int pivotIndex = (left + right)/2;        
    pivotIndex 
= partition(data, left, right, pivotIndex);        
    quickSort(data, left, pivotIndex 
- 1);        
    quickSort(data, pivotIndex 
+ 1, right);   
}                        
//将小于data[pivotIndex]的放在左边,其余放在右边,并返回中间位置  

int partition(int data[], int left, int right, int pivotIndex)    
{        
    
int storeIndex = left;        
    
int pivot = data[pivotIndex];        
    data[pivotIndex] 
= data[right];        
    data[right] 
= pivot;    

    
for(int i = left; i < right; i++)        
    {           
        
if(data[i] < pivot)            
        {                
            
int tmp = data[storeIndex];                
            data[storeIndex] 
= data[i];               
            data[i] 
= tmp;               
            storeIndex
++;            
        }        
    }     

    data[right] 
= data[storeIndex];        
    data[storeIndex] 
= pivot;        
    
return storeIndex;   
}

 

四、插入排序

  1、直接插入排序

    有点类似于打扑克时候,每抓一张牌把小的放左边,大的放右边。也即手上的这部分牌永远是从小到大排好序的,每抓一张牌均依次跟最右边的比较,知道找到一张比它小的或相等的,插在这张牌的后面。

代码
void InsertionSort(int *data, int len)  
{  
    
if(len <= 1return;  

    
for(int i = 1; i < len; i++)  

    {  
         
int value = data[i];  
         
int j = i - 1;                   //注:若能将data的第0个元素作为哨兵,待排序从第1个元素起,那么每次可以把data[0]=data[i],直接判断data[j]>data[0],从而不必要判断j>=0  

         
while(j >= 0 && data[j] > value)      
         {  
            data[j 
+ 1= data[j];            //大的记录往后移  
            j--;  
         }  

        data[j 
+ 1= value;                //插入记录  

    }  
 } 

 

  2、二分直接插入排序法

    直接插入排序法中每次遇到第i+1个记录,均需顺序与前面i, i-1,...个记录去比较,由于前面i个记录已经是排序好的了,故可以参考二分查找的方式进行,即先与第(i+1)/2个记录比较,从而逐渐缩小范围,减少比较次数。

  3、表插入排序

    对于顺序存取的数组,每次插入的过程都伴随着大量的记录后移操作,为此我们可以考虑改造记录的类型结构,采用双向链表,这样就可以免去了移动操作,只需要修改目标位置前后的指针值就可以了。

 

   五、SHELL排序

     Shell排序法又称缩小增量法,由D.L.Shell在1959年提出,是对直接插入排序法的改进。其主要思想是:直接插入排序中,当初始序列为逆序时,时间效率最差。若初始序列基本有序时,则大多数记录不需要插入,时间效率大大提高。另外,当记录数n较小时,n2值受n的值影响不大。Shell排序正是从这两个方面考虑对直接插入排序进行改进。

  1)先取一个整数d1<n, 把全部记录分成d1个组,所有距离为d1倍数的记录放在一组中,先在各组内排序

  2)然后取d2<d1重复上述分组和排序工作

  3)直到di=1,即所有记录放在一组中为止

  各组内的排序可以采用直接插入法,也可以采用后面讲到的其它排序方法,如直接选择排序

  对于增量的选择:Shell提出:d1 = n/2, d2=d1/2,依次类推;Knuth提出d1=n/3,d2=d1/3,依次类推。最后增量为1.还有其它选择,但何种最佳尚无法证明。无论如何,最后增量必须为1。

代码
void ShellSort(int data[], int length)  
{  
    
for(int inc = length / 2; inc > 0; inc /= 2)  
    {  
       
for(int i = inc; i < length; i++)                //类似于插入排序,相当于根据每隔inc个抽取形成一组,然后应用插入排序方法对每组进行排序,这里从每组的第2个开始循环  
         {                                              //对于每一条记录,按照inc的间隔插入前面的记录中  
             int value = data[i];  
             
int j = i - inc;  

             
while(j >= 0 && data[j] > value)  
             {  
                  data[j 
+ inc] = data[j];              //关键字跳跃式前进
                  j 
-= inc;  
             }  

             data[j 
+ inc] = value;  
         }  
    }  

 

 

 

 

 

 

posted on 2010-05-12 22:54  赵情融  阅读(395)  评论(0编辑  收藏  举报