八大排序简单小结及c++实现

         即插入排序、冒泡排序、选择排序、shell排序、基数排序、归并排序、快速排序、堆排序

、(直接)插入法(交换排序) 

1原理方法

       从第二个数开始与前面的一个一个比较,小于则交换、大于等于则下一个数的循环。

2、特点

1)、稳定性:稳定

2)、时间代价:O(n*n)

    最好——正序——时间代价Θ(n)    

    最差——倒序——时间代价Θ(n*n)  

    平均——乱序——时间代价Θ(n*n) 

3)、辅助存储空间:O(1)

4)、比较

①较为复杂、速度较慢

②n较小时(<=50) 、局部或整体有序时适用

   插入排序的最佳时间代价特性——基本有序。

③循环交换

循环不同:f(n)<=1/2*n*(n-1)<=1/2*n*n

交换不同(赋值操作)

3、代码

#include <iostream>
using namespace std;

void InsertSort(int*  , int );
int main() 
{ 	
    int data[]={1,-30,12,7,-1,5,4};
    InsertSort(data,7);
    for(int i=0;i<7;i++)
        cout<<dec<<data[i]<<"  ";
        cout<<"\n";
    //system ( "pause" );//getchar();
    return 0;
 } 

void  InsertSort(int* pData,int Count)
{
    int iTemp;
  int iPos;
    for(int i=1;i<Count;i++)
    {
        iPos=i-1;
        iTemp=pData[i];
        while((iPos>=0)&&(iTemp<pData[iPos]))
        {
            pData[iPos+1]=pData[iPos];
            iPos--;
        }
        pData[iPos+1]=iTemp; 
    }
}

二、 冒泡法( 交换排序 )

1原理方法

1)、把小的元素往前调或者把大的元素往后调, 一趟得到一个最大值或最小值。

          若循环外设一个bool变量、最差(倒序)循环n-1趟、最好(正序)循环一趟

2)、有递归和非递归实现

2特点

1)、稳定性:稳定

2)、时间代价

    最好——正序、无交换(O(0))——时间代价Θ(n)(只循环一趟)

    最差——倒序、循环次数=交换次数(O(n*n))——时间代价Θ(n*n)  

    平均——乱序、中间状态                  ——时间代价Θ(n*n)  

3)、辅助存储空间:O(1)

4)、比较

①速度较慢、交换次数相对比较多

②n较小时(<=50) ,局部或整体有序时、较快

③循环交换

循环相同(若循环外不设判断条件)

           1+2+...+n-1=1/2*(n-1)*n<=1/2*n*nK*g(n)

              f(n)O(g(n))O(n*n)(循环复杂度)

交换不同

3、代码

1)、非递归循环实现

#include <iostream>
using namespace std;

void BubbleSort(int*  , int );
int main() 
{ 	
    int data[]={10,9,13,7,32,5,2};
    BubbleSort(data,7);
    for(int i=0;i<7;i++)
        cout<<dec<<data[i]<<"  ";
        cout<<"\n";
    return 0;
 } 

void  BubbleSort(int* pData,int n)
{
  int iTemp;

    bool bFilish=fale;
    for(int i=0;i<n-1;i++)
  {
        if(bFlish=true)
            break;
        bFilish=true;
        for(int j=n-1;j>i;j--)
        {
            if(pData[j]<pData[j-1])
            {
                iTemp=pData[j-1];
                pData[j-1]=pData[j];
                pData[j]=iTemp;
                bFilish=false;
            }

        }
    }
}


2)、递归,下面采用双向冒泡
#include <iostream>
using namespace std;

void BilateralBubbleSort(int*  , int,int );
int main() 
{ 	
    int data[]={10,9,13,7,32,5,2};
    BilateralBubbleSort(data,0,6);
    for(int i=0;i<7;i++)
        cout<<dec<<data[i]<<"  ";
        cout<<"\n";
    return 0;
} 

void BilateralBubbleSort(int *a, int first, int last) 
{
    if (first >= last) //退出条件
    {                          
         return;
    }

    int i = first;
    int j = last;
    
    int temp = a[first];    
    while (i != j) 
    {                            
        while (i<j && a[j]>=temp)  
        {
            j--;
        }
        a[i] = a[j];
        while (i<j && a[i]<=temp) 
        {
            i++;
        }
        a[j] = a[i];
    }
    a[i] = temp;

    //递归,有点像快速排序,就是中间值放在临时变量而不是数组尾部
    BilateralBubbleSort(a, first, i-1);     
    BilateralBubbleSort(a, i+1, last);
  
}


三、(简单或直接)选择法( 交换排序 )

1原理方法

1)、第一个元素开始,同其后的元素比较并记录最小值(放在当前位置)

         每次得到一个最小值

2)、(改进)选择中间变量、减少交换次数

2特点

1)、稳定性:不稳定

2)、时间代价:O(n*n)

    最好——正序、无交换(O(0))                

    最差——倒序、循环次数=交换次数

    平均—— 乱序、中间状态                  

3)、辅助存储空间:O(1)

4)、比较

①速度较慢

②与冒泡法某些情况下稍好,在某些情况下稍差

  这3种中是很有效的, n较小时(<=50) 适用

③循环交换

循环相同:

         1/2*(n-1)*n

交换不同

3、代码

#include <iostream>
using namespace std;

void SelectSort(int*  , int );
int main() 
{ 	
    int data[]={1,9,12,7,26,5,4};
    SelectSort(data,7);
    for(int i=0;i<7;i++)
	cout<<dec<<data[i]<<"  ";
    cout<<"\n";
    return 0;
} 

void  SelectSort(int* pData,int Count)
{
    int iTemp;
    for(int i=0;i<Count-1;i++)
    {
	for(int j=i+1;j<Count;j++)
	{
	    if(pData[i]<pData[j])
	    {
		iTemp=pData[i];
		pData[i]=pData[j];
		pData[j]=iTemp;
	    }
	}
    }
}

四、快速排序

 1、原理方法

1)、分治法

2)、二叉查找树

像一个二叉树、递归实现

①(分割)首先选择一个轴值,如放在数组最后

②把比它小的放在左边,大的放在右边(两端移动下标,找到一对后交换)。

③直到相遇、返回下标k(右半部起始、即轴值下标)

④然后对两边分别使用这个过程

3)、轴值的选取

①第一个记录的关键码(正、逆序时有问题)

②随机抽取轴值(开销大)

③数组中间点(一般)

4)、最理想的情况
①数组的大小是2的幂,这样分下去始终可以被2整除

    假设为2k次方,即klog2(n)。 

②每次我们选择的值刚好是中间值、数组可以被等分。 

第一层递归,循环n次,第二层循环2*(n/2)...... 
所以共有n+2(n/2)+4(n/4)+...+n*(n/n) = n+n+n+...+nk*nlog2(n)*n 
所以算法复杂度为O(n*log2(n)) 

其他的情况只会比这种情况差,最差的情况是每次选择到的middle都是最小值或最大值,那么他将变成交换法(由于使用了递归,情况更糟)。

 2、特点

1)、稳定性:不稳定

2)、时间代价:O(n*logn) 

   最差——O(n*n) 

    平均——O(n*logn),介于最佳和最差之间

    最好——O(n*logn)

3)、辅助存储空间:O(logn)

4)、比较

 ①局部或整体有序时慢

 ②(大多数情况)平均最快

   n(9)较大时、关键字元素比较随机(杂乱无序)适用

 ③分割数组,多交换、少比较

3、代码

1)、一般的方法

#include<iostream>
using namespace std;
typedef int* IntArrayPtr;

int Divide(int a[],int left,int right)
{
    int k=a[left];  //轴值
    do
    {
	while(left<right&&a[right]>=k) --right;
        if(left<right)
        {
	    a[left]=a[right];
	    ++left;
        }
      
	while(left<right&&a[left]<=k)++left;
	if(left<right)
	{
	    a[right]=a[left];
	    --right;
	}
    }while(left!=right);
    //排序后轴值位置,分为两个子序列
    a[left]=k;
    return left;
}

void QuickSort(int a[],int left,int right)
{
    int mid;
    if(left>=right)return;

    mid=Divide(a,left,right);
    cout<<a[mid]<<'\n';
    for(int j=left;j<=right;j++)
	cout<<a[j]<<" ";
    cout<<'\n';

        //轴值左半部分
    QuickSort(a,left,mid-1);
        //轴值右半部分
    QuickSort(a,mid+1,right);
}

void FillArray(int a[],int size)//输入数据
{
    cout<<"请输入"<<size<<"个整数,数字之间用空格隔开:"<<endl;
    for(int index=0;index<size;index++)
	cin>>a[index];
}

void main()
{
    int array_size;
    cout<<"请输入需要排序的元素个数:";
    cin>>array_size;
    IntArrayPtr a;
    a=new int[array_size];//动态数组
	
    FillArray(a,array_size);
    cout<<'\n'<<"快速排序开始:"<<endl;
    
    QuickSort(a, 0, array_size-1);
    cout<<"end"<<'\n';
    for(int k=0;k<array_size;k++)
    {
	cout<<a[k]<<" ";
    }
    cout<<'\n';
    system("pause");//getchar();
}

2)、剑指offer上一个比较好的方法,保存了数组的两个位置index向前遍历数组,small用于保存交换的小于轴值的数,找到一个前移一步。

#include "stdafx.h"
#include <stdlib.h>
#include <exception>

int RandomInRange(int min, int max)   //随机轴值
{
    int random = rand() % (max - min + 1) + min;
    return random;
}

void Swap(int* num1, int* num2)
{
    int temp = *num1;
    *num1 = *num2;
    *num2 = temp;
}

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 small = start - 1;
    for(index = start; index < end; ++ index)   
    {
        if(data[index] < data[end])
        {
            ++ small;
            if(small != index)   
                Swap(&data[index], &data[small]);
        }
    }

    ++ small;     
    Swap(&data[small], &data[end]);

    return small;
}

//递归快速排序
void QuickSort(int data[], int length, int start, int end)
{
    if(start=end)
        reurn;

    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);
}


五、Shell排序(缩小增量排序)(局部用的插入排序)

1、原理方法

       由于复杂的数学原因避免使用2的幂次步长,它能降低算法效率

        子序列——每轮等数量(大到小)、等增量、等长度——插入排序——合并再重复

实现:


(1)初始增量为3,该数组分为三组分别进行排序。(初始增量值原则上可以任意设置 

    (0<gap<n),没有限制)

2)将增量改为2,该数组分为2组分别进行排序。

3)将增量改为1,该数组整体进行排序。

 

2、特点

1)、稳定性:不稳定

2)、时间代价——依赖于增量序列

                                                   最好                            最差

 

  平均——()增量除3时是On1.5)、O(n*logn}~On2

3)、辅助存储空间:O(1)

4)、比较

    ①规模非常大的数据排序不是最优选择

    ②n为中等规模时是不错的选择

 3、代码

#include <iostream>
using namespace std;

int a[] = {70,30,40,10,80,20,90,100,75,60,45};
void shell_sort(int a[],int n);

int main()
{
    cout<<"Before Sort: ";
    for(int i=0; i<11; i++)
	cout<<a[i]<<" ";
    cout<<endl;
    shell_sort(a, 11);
    cout<<"After Sort: ";
    for(int i=0; i<11; i++)
	cout<<a[i]<<" ";
    cout<<endl;
    system("pause");  //getchar();//gcc中没有system("pause");命令
}

void shell_sort(int a[], int n)
{
    for(int gap = 3; gap >0; gap--)
   {
       for(int i=0; i<gap; i++)
       {
	    for(int j = i+gap; j<n; j=j+gap)
	   {
		if(a[j]<a[j-gap])
		{
		    int temp = a[j];
		    int k = j-gap;
		    while(k>=0&&a[k]>temp)
		    {
			a[k+gap] = a[k];
			k = k-gap;
		    }
		    a[k+gap] = temp;
		}
	    }
        }
    }
}

六、归并排序

 1、原理方法

分治法、归并操作算法)、稳定有效的排序方法(直接插入)、等长子序列

1归并操作

设有数列{62021003013881}

初始状态:6,202,100,301,38,81

第一次归并后:{6,202},{100,301},{8,38},{1},比较次数:3

第二次归并后:{6,100,202,301}{1,8,38},比较次数:4

第三次归并后:{1,6,8,38,100,202,301},比较次数:4

总的比较次数为:3+4+4=11,

逆序数为14

2)、将已有序的子序列合并,得到完全有序的序列若将两个有序表合并成一个有序表,称为二路归并

3)、非递归算法实现

假设序列共有n个元素

①将序列每相邻两个数字进行归并操作(merge),形成floor(n/2)个序列,排序后每个序列包含两个元素

②将上述序列再次归并,形成floor(n/4)个序列,每个序列包含四个元素

③重复上面步骤,直到所有元素排序完毕

 2、特点

1)、稳定性:稳定

2)、时间代价:O(n*logn)

3)、辅助存储空间:O(N)

4)、比较

①空间允许的情况下O(n)

②速度仅次于快速排序、n较大有序时适用

排序:一般用于对总体无序,但是各子项相对有序的数列

求逆序对数:具体思路是,在归并的过程中计算每个小区间的逆序对数,进而计算出大区间的逆序对数

3、代码

#include <iostream>
#include <ctime>
#include <cstring>
using namespace std;

void Merge(int* data, int a, int b, int length, int n)
{
    int right;
  if(b+length-1 >= n-1)
      right = n-b;
  else 
      right = length;
  
    int* temp = new int[length+right];
  int i = 0, j = 0;
  
  while(i<=length-1&&j<=right-1)
  {
        if(data[a+i] <= data[b+j])
        {
            temp[i+j] = data[a+i]; 
            i++; 
        }
        else
        { 
            temp[i+j] = data[b+j]; 
            j++; 
        }
    }

  if(j == right)
  {
        memcpy(data+a+i+j, data+a+i,(length-i)*sizeof(int));
  }

    memcpy(data+a, temp, (i+j)*sizeof(int) );
    delete temp;
}

void MergeSort(int* data, int n)
{
  int step = 1;
  while(step < n)
  {
        for(int i = 0; i <= n-1-step; i += 2*step)
            Merge(data, i, i+step, step, n);
        step *= 2;
    }
}

int main()
{
    int n;
    cin >> n;
    int *data = new int[n];
  if(!data) 
      exit(1);
    int k = n;
  while(k --)
  {
        cin >> data[n-k-1];
  }
  
  clock_t s = clock();
  MergeSort(data, n);
  clock_t e = clock();
  
    k = n;
  while(k --)
  {
        cout << data[n-k-1] << ' ';
    }
    cout << endl;
    cout << "the algrothem used " << e-s << " miliseconds."<< endl;
    delete data;
  return 0; 
}

七、堆排序

1、原理方法

    BST、堆数据结构、利用数组快速定位、无序有序区

 

2、特点

1)、稳定性:不稳定

2)、时间代价:O(n*logn)

3)、辅助存储空间:O(1)

4)、比较

①常情况下速度要慢于快速排序(因为要重组堆)

②既能快速查找、又能快速移动元素。

     n较大时、关键字元素可能出现本身是有序时适用


 3、代码

#include<iostream>
#include <ctime>
#include <cstring>
using namespace std;

void HeapAdjust(int array[], int i, int nLength)
{
    int nChild;
  int nTemp;
  
    for (nTemp = array[i]; 2 * i + 1 < nLength; i = nChild)
    {
        nChild = 2 * i + 1;

        if ( nChild < nLength-1 && array[nChild + 1] > array[nChild])
            ++nChild;

        if (nTemp < array[nChild])
        {
            array[i] = array[nChild];
            array[nChild]= nTemp;
        }
        else
            break;
    }
}

// 堆排序算法
void HeapSort(int array[],int length)
{  
    int tmp;
    for (int i = length / 2 - 1; i >= 0; --i)
        HeapAdjust(array, i, length);

    for (int i = length - 1; i > 0; --i)
    {
        tmp = array[i];
        array[i] = array[0];
        array[0] = tmp;
        HeapAdjust(array, 0, i);
    }
}

int main()
{
    int n;
    cin >> n;
    int *data = new int[n];
    if(!data) 
        exit(1);
    int k = n;
    while(k --)
    {
         cin >> data[n-k-1];
    }

    clock_t s = clock();
    HeapSort(data, n);
    clock_t e = clock();

    k = n;
    while(k --)
    {
        cout << data[n-k-1] << ' ';
    }
    cout << endl;
    cout << "the algrothem used " << e-s << " miliseconds."<< endl;
    delete data;
    system("pause");
}

八、 基数排序(桶排序)(属于分配排序)

1、原理方法

     基数排序(radix sort)属于分配式排序distribution sort、又称桶子法bucket sortbin sort)。通过键值的查询,将要排序的元素分配至某些“桶”中,以达到排序的作用。

1)、分配排序(hash)

①关键码确定记录在数组中的位置,但只能对0n-1进行排序

②(扩展)允许关键码重复、数组元素可变长、每个元素成为链表的头节点

③(扩展)允许关键码范围大于n、关键码值(盒子数)比记录数大很多时效率很差(检查是否有元素)、同时存储的数组变大

④(扩展)桶式排序、每一个盒子与一组关键码相关、桶中(较少)记录用其它方法(收尾排序)排序

⑤堆排序

2)、LSD的基数排序

适用于位数小的数列

73, 22, 93, 43, 55, 14, 28, 65, 39, 81

首先根据个位数的数值,在走访数值时将它们分配至编号09的桶子中

0

1 81

2 22

3 73 93 43

4 14

5 55 65

6

7

8 28

9 39

接下来将这些桶子中的数值重新串接起来,成为以下的数列

81, 22, 73, 93, 43, 14, 55, 65, 28, 39

接着再进行一次分配,这次是根据十位数来分配:

0

1 14

2 22 28

3 39

4 43

5 55

6 65

7 73

8 81

9 93

接下来将这些桶子中的数值重新串接起来,成为以下的数列:

14, 22, 28, 39, 43, 55, 65, 73, 81, 93

这时候整个数列已经排序完毕;如果排序的对象有三位数以上,则持续进行以上的动作直至最高位数为止。

3)、MSD

①位数多由高位数为基底开始进行分配

②分配之后并不马上合并回一个数组中,而是在每个“桶子”中建立“子桶”,将每个桶子中的数值按照下一数位的值分配到“子桶”中。在进行完最低位数的分配后再合并回单一的数组中。

 

2、特点

1)、稳定性:稳定

2)、时间代价

n个记录、关键码长度为d(趟数)基数r(盒子数如10)、不同关键码值m(堆数<=n)

②链式基数排序的时间复杂度为O(d(n+r))

一趟分配时间复杂度为O(n)、一趟收集时间复杂度为O(radix)、共进行d趟分配和收集

③下面是一个近似值,可自己推导

O(nlog(r)m)、O(nlogn)(关键码全不同)

m<=nd>=log(r)m

3)、辅助存储空间

2*r个指向队列的辅助空间、用于静态链表的n个指针

4)、比较

    ①空间允许情况下

    ②适用于:

      关键字在一个有限范围内

      有些情况下效率高于其它比较性排序法

      记录数目比关键码长度大很多

      调节r得到较好性能

3、代码

int MaxBit(int data[],int n) 
{
    int maxBit = 1; 
    int temp =10;
    for(int i = 0;i < n; ++i)
    {
        while(data[i] >= temp)
        {
            temp *= 10;
            ++maxBit;
        }
    }
    return maxBit;
}

//基数排序
void RadixSort(int data[],int n)
{
    int maxBit = MaxBit(data,n);

    int* tmpData = new int[n];   
    int* cnt = new int[10];  
  int  radix = 1;       
  int  i,j,binNum;      
  
    for(i = 1; i<= maxBit;++i) 
    {
        for(j = 0;j < 10;++j)
            cnt[j] = 0; 

        for(j = 0;j < n; ++j)
        {
            binNum = (data[j]/radix)%10; 
            cnt[binNum]++;
        }

        for(binNum=1;binNum< 10;++binNum)
            cnt[binNum] = cnt[binNum-1] + cnt[binNum]; 

        for(j = n-1;j >= 0;--j) 
        {
            binNum= (data[j]/radix)%10;            
            tmpData[cnt[binNum]-1] = data[j];    
            cnt[binNum]--;                        
        }

        for(j = 0;j < n;++j)
            data[j] = tmpData[j];
        radix = radix*10;
    }
    delete [] tmp;
    delete [] cnt;
}






  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值