排序

按照C的约定,对于所有排序,数据都将在位置0处开始。

对于数字可使用“<”和“>”;对于字符串使用strcmp和strcpy。

排序法

平均时间

最差情形

稳定度

额外空间

备注

冒泡

O(n2)

O(n2)

稳定

O(1)

n小时较好

插入

O(n2)

O(n2)

稳定

O(1)

大部分已排序时较好

Shell

O(nlogn)

O(n^1.25)

O(ns) 1<s<2

不稳定

O(1)

s是所选分组

快速

O(nlogn)

O(n2)

不稳定

O(nlogn)

n大时较好

归并

O(nlogn)

O(nlogn)

稳定

O(1)

n大时较好

O(nlogn)

O(nlogn)

不稳定

O(1)

n大时较好

一、冒泡排序

作为排序的启蒙算法,回顾一下逝去的青春岁月。。。

void bubbleSort(elementType A[], int N)
{
       int i,j;
       for(i = 0; i < N; i++)    //第一趟,A[0]与其余元素比较,将最小元素交换给A[0]
       for(j = i+1; j < N; j++)
       {
           if(A[i] > A[j])
           swap(&A[i], &A[j]);
       }
}
void swap(elementType *a,elementType *b)
{
       elementType c;
        c =  *a;         //字符串,可以使用strcpy(c, *a);  
       *a =  *b;
       *b =   c;
}

二、插入排序

插入排序有N-1趟排序组成。对于P = 1趟 到P = N-1趟,插入排序保证对于每趟P,位置0到位置P上的P+1个元素已排序。

由此可知:

对于每趟P,位置P之前的元素已经为有序状态。只需要将位置P上的元素保存并依次与其前元素比较,比其大的元素依次后移,将位置P上元素插入到合适位置,保证前P+1个元素为有序状态。

void insertionSort( elementType A[] , int N)
{
       int j ,p;
       elementType tem;
       for( p = 1 ; p <= N-1; p++)
       {
            tem = A[p];      //保存位置p上的数据到tem,则位置p为空位,其前元素大于tem的,则依次后移一位。
            for(j = p; j - 1>=0 && A[j-1] > tem; j--)
                A[j] = A[j-1];
            A[j] = tem;
       }
}

三、希尔排序

插入排序是连续的子序列,而希尔排序是跳跃的子序列,跳跃的幅度取决于增量大小。

其中增量为Hibbard增量时,最坏时间复杂度为Θ(N3/2)。

Hibbard增量形如:1 , 3 , 7 , ... , 2k-1. (增量之间没有公因子)

void shellSort (elementType A[], int N)
{
        int i , j , increment;
        elementType tem;
        //prevPrime(S)表示小于S的最大素数
        for( increment = prevPrime(N/2); increment > 0; increment /=2)
              for( i = increment; i < N; i++)
              {
                   tem = A[i];
                   for(j = i; j >=increment; j -= increment)
                       if(tem < A[j - increment])
                          A[j] = A[j - increment];
                       else
                           break;
                   A[j] = tem;
              }
              
}

 四、堆排序

堆是一个完全二叉树,可以用一个数组表示;

a[0]存放一个值(最大堆时,这个值要足够大),堆中元素从下标1开始存储。

对于任一个父亲结点:i,左孩子:2*i,右孩子:2*i+1;

对于任一个孩子结点:i,其父亲结点都是i/2;

两个思路:

1、建立最大堆,每次删除堆顶元素,保存到新的数组,新的数据就是降序排列,将数组复制回原数组即可。由于建立新的数组,空间复杂度增加了。

2、在建立最大堆后,每次删除堆顶元素,保存到堆的末尾处(2-1指针和2-2数组两种方式)

2-1:指针方式

插入即之前二叉堆的上滤的过程:

for (i = ++H->size; H->elements[i/2] > X; i /= 2 )
      H->elements[i] = H->elements[i/2];
H->elements[i] = X;

这样就建立了一个最小堆。

在删除最小元素时,需要将代码稍加修改:

a. 堆的原始大小H->size保持不变,可以用一个整型替代,并以形参传递

b. 保持每次删除的最小元素

elementType deleteMin(priorityQueue *H, int& N)
{
         int i, child;
         if(isEmpty(H))
         {
               cout << "priotity queue is empty" << endl;
               return H->elements[0];
         }
         minElement = H->elements[1];
         lastElement = H->elements[ N-- ];
         for(i = 1; 2*i <= N; i = child)
         {
               child = 2*i;
               if(child != N && H->elements[child +1] < H->elements[child])
                  child ++;
               if(H->elements[child] < lastElement)
                  H->elements[i] = H->elements[child];
               else             //不写的话,在最小堆下,i的取值也不会满足循环条件
                    break;   
         }
         H->elements[i] = lastElement;
         return minElement;
}

堆排序:

priorityQueue* heapSort(priorityQueue *H)
{
           for(int N = H->size; N > 0; N--)
           {
               H->elements[++N] = deleteMin(H, N);  //N是实参,即H->size
       }
      
return H;
}

2-2数组方式

直接进行堆排序,即默认已经存在一个完全二叉树,我们需要将其排成最大堆(孩子与父亲结点的交换),

然后将最大元素放在数组末端,即交换第一个和最后一个元素,然后对前N-1个元素再一次建立最大堆

所以,先写一个建立最大堆的公共接口。

建立最大堆的方法:即最大堆原始定义:孩子结点都小于父亲结点,否则交换两者数据;

技巧:如果存在两个孩子选出最大的出来,跟父亲结点交换。

从下标0开始存储数据,此时,对于元素i,左儿子2*i+1,右儿子2*i+2;

#define leftChild(i)  (2*(i)+1)  //i = N-1时,(i)的优势就体现出来了

void interChange(elementType A[], int i, int N)
{
        int child;
        elementType tem;
        for(tem = A[i]; leftChild(i) < N; i = child)
        {
              child = leftChild(i);
              if(child != N && A[child+1] > A[child])
                child++;
              if(tem < A[child])      //保证在任意数组下,建立起最大堆
                A[i] = A[child];
              else
                   break;
        }
         A[child] = tem;
}
void heapSort (elementType A[], int N)
{
       int i; 
       for(i = N/2; i >=0; i--)  //也可以用for(i = 0;2*i<N;i++)
            interChange(A, i, N);     //builfMaxHeap

       for(i = N-1; i > 0; i--)
       {
             swap(&A[0], &A[N-1]);        //deleteMax
             interChange(A, 0, i );            //在堆序性下,删除了最大元素后,只需要调整堆顶及其孩子的堆序即可
       }     
}

五、归并排序

归并排序以O(NlogN)最坏情形运行时间运行。比较次数几乎是最优的。他是分治思想的一个好例子。

若N=1,则无需排序。否则,递归地将前半部分数据后半部分数据 各自归并排序,最后将这两部分合并。

合并方法:

从头依次比较 前后两半数据的数组A和B 并将较小者保存至 一个新的数组C中,下标自增;

直到A或B其中一个数组中元素全部在C中,最后将剩余有序元素依次保存在C中。

改进的方法就是将原始数组分为A和B两个数组,最后将C赋值回原数组即可。

//归并是左右开弓,无限拆分;实际排序部分由merge()完成。

void mSort(elementType A[], elementType tem[], int left, int right)
{
       int center;
       if(left < right)        //left == right,只有一个元素在A中,无需排序
       {
          center = (left + right)/2;
          mSort(A, tem, left, center);           //左部分排序  这些都是实参,即实际数值
          mSort(A, tem, center+1, right);    //右部分排序
          merge(A, tem, left, center+1, right); //left:左开,center:左结束;center+1:右开,right:右结束
       }
}

//主程序
void mergeSort(elementType A[], int N)
{
       elementType *tem;
       tem = (elementType *)new elementType[N];
       if(tem == NULL)
       {
          cout <<"out of space!" << endl;
       }    
       else
       {
           mSort(A, tem, 0, N-1);         //主程序前,先定义mSort();
           delete(tem);                   //指针回收
       }
}

//合并程序merge的实现
void merge(elementType A[], elementType tem[], int leftStart, int rightStart, int rightEnd)
{
       int i, leftEnd, totalNum,temStart;
       leftEnd = rightStart - 1;
       temStart = leftStart;           //原数组排序未必从位置0开始,tem数组的起始位置跟原始数组保持一致,方便最后回传数据
       totalNum = rightEnd - leftStart +1;
       //main loop
       while(leftStart <= leftEnd && rightStart <= rightEnd)
             if(A[leftStart] <= A[rightStart])
                tem[temStart++] = A[leftStart++];
             else  
                tem[temStart++] = A[rightStart++];
       //the rest elements
       while(leftStart <= leftEnd)
             tem[temStart++] = A[leftStart++];
       while(rightStart <= rightEnd)     
             tem[temStart++] = A[rightStart++];
        //return data back to A
        for(i = 0; i < totalNum; i++, rightEnd--)  //以上变量只有,leftEnd和rightEnd值为变,由于原始数组未必从0开始,所以不可以从0开始赋值
            A[rightEnd] = tem[rightEnd];
}

 六、快速排序

quickSort是实践中最快的已知排序算法,平均运行时间O(NlogN)。

1、如果A中元素个数为0或1,则返回。

2、在A中取一个枢纽元pivot,利用三数中值分割法,将left,center,right上的元素进行比较,A[left]最小,A[right]最大。swap(&A[center],&A[right-1])将pivot保存在A[right]中。

3、N<=3,插入排序;N>=4快排。

三数中值分割法:

elementType median3(elementType A[], int left, int right)
{
             int center;
             center = (left + right) / 2;
             if(A[left] > A[center])
                swap(&A[left], &A[center]);
             if(A[center] > A[right])
                swap(&A[center], &A[right]);
             if(A[left] > A[center])
                swap(&A[left], &A[center]);
             swap( &A[center], &A[right - 1]);  //将pivot保存在A[right-1]中
             return A[right - 1];
}

主程序:

#define offset (3)
void quickSort(elementType A[], int left, int right)
{
        int i, j;
        elementType pivot;
        if(left + offset <= right)    //right - left + 1 >= 4;
        {
           pivot = median3(A, left, right);
           i = left; 
           j = right - 1;
           while(1)
           {
                while (A[++i] < pivot){}
                while (A[--j] > pivot){}
                if(i < j)
                   swap(&A[i], &A[j]);
                else
                   break;  //最后由于 ++i 终止,则 i == j;最后由于--j 终止,则 i - 1 = j。 A[i]中保存着从左到右第一个大于pivot的数,交换A[i]和A[right - 1]值,将pivot值保存在A[i]中。
           }
           swap(&A[i]) , &A[right - 1]);  //将pivot值保存在A[i]中
           quickSort(A, left, i - 1);    
           quickSort(A, i +1, right);
         }
         else 
             insertSort(A, right - left +1);
}

快排-剑指版:

数组中随机找一个数字将数组分成两部分,使得比选择数字小的移到数组左边,比选择数字大的移到数组右边。

int index = start + rand()%(end - start + 1);  index属于[start , end]区间的一个随机数;

int Partition(int data[], int length, int start, int end)
{
     if(data == NULL || length <= 0 || start < 0 || end >= length)
         throw new std::exception("Invalid Parameters");  
     int index = RandomInRange(start , end);   //随机找一个值
     Swap(&data[index] , &data[end]);             //将随机找的元素放在最后一个

      int pivot = start - 1;     //用于保存随机元素的位置
      for(index = start; index < end; ++index)
      {
           if(data[index] < data[end])
           {
              ++pivot;                             //pivot存放最后一个比随机值小的数字
              if(pivot != index)
                 Swap(&data[index] , &data[pivot]);
           } 

}
++pivot; //pivot是第一个大于随机数的下标 Swap(&data[pivot] , &data[end]); return pivot; //随机值的下标 }
void QuickSort(int data[], int length, int left, int right)
{
        if (start == end)       //异常情况
            return;
        int index = Partition(data, length, start, end);  //返回随机值下标,并左右满足快排规则
        if(index > start)
           QuickSort(data, length, start, index - 1);  
        if(index < end)
           QuickSort(data, length, index+1, end);
}
原文地址:https://www.cnblogs.com/Lunais/p/5604343.html