数据结构中的参见排序算法的实现,以及时间复杂度和稳定性的分析(2)

  

数据结构测参见算法分类如下(图片来源https://www.cnblogs.com/hokky/p/8529042.html)  

 

3.直接选择排序:每次查找当前序列中的最小元素,然后与序列头进行交换,再查询剩余序列中的长度,依次类推下去。

代码如下:

#include <iostream>

using namespace std;

int main()
{
    int s[]={10,9,8,7,6,5,4,3,2,1};
    for(int i=0;i<10;i++)
    {
        int min=i;
        for(int j=i+1;j<10;j++)
        {
            if(s[j]<s[min])
                min=j;
        }
        cout<<"s[min]: "<<s[min]<<endl;
        int temp=s[min];
        s[min]=s[i];
        s[i]=temp;
    }

    for(int i=0;i<10;i++)
    {
        cout<<s[i]<<" ";
    }
    cout<<endl;
    return 0;
}

不管初始序列是否有序,其时间复杂度都是O(n*2),当两个元素相同时,比如 2,2,3,1,第一次遍历后,会变为1,2,3,2  可见两个2的相对位置发生了改变,所以是不稳定的。

 

4.堆排序:分为大顶堆和小顶堆,比如大顶堆就是根节点的值是大于其左右子树上元素的值。堆排序(大顶堆)分为两步:第一步是构建初始堆,并将其调整为大顶堆。第二步是将堆顶元素取出,放到序列末尾,再调整堆为大顶堆。一直重复上面两步,直到堆中的元素为空。  代码如下:

#include <iostream>

using namespace std;

void adjust(int * s,int len,int index)
{
    int left=2*index+1;
    int right=2*index+2;
    int maxIndex=index;
    if(left<len&&s[left]>s[maxIndex])
        maxIndex=left;
        
    if(right<len&&s[right]>s[maxIndex])
        maxIndex=right;

    if(maxIndex==index)
        return;

    int temp=s[maxIndex];
    s[maxIndex]=s[index];
    s[index]=temp;
    adjust(s,len,maxIndex);
}

void heap_sort(int * s,int len)
{
    for(int i=len/2-1;i>=0;i--)
    {
        adjust(s,len,i);
    }

    for(int i=len-1;i>=1;i--)
    {
        int temp=s[i];
        s[i]=s[0];
        s[0]=temp;

        adjust(s,i,0);
    }
}

int main()
{
    int s[]={10,9,8,7,6,5,4,3,2,1,12,11};
    heap_sort(s,12);
    for(int i=0;i<12;i++)
        cout<<s[i]<<" ";
    cout<<endl;
    return 0;
}

第一步构建初始堆(转载于https://blog.csdn.net/loveliuzz/article/details/77618530

假设高度为k,则从倒数第二层右边的节点开始,这一层的节点都要执行子节点比较然后交换(如果顺序是对的就不用交换);倒数第三层呢,则会选择其子节点进行比较和交换,如果没交换就可以不用再执行下去了。如果交换了,那么又要选择一支子树进行比较和交换;

        那么总的时间计算为:s = 2^( i - 1 )  *  ( k - i );其中 i 表示第几层,2^( i - 1) 表示该层上有多少个元素,( k - i) 表示子树上要比较的次数,如果在最差的条件下,就是比较次数后还要交换;因为这个是常数,所以提出来后可以忽略;

        S = 2^(k-2) * 1 + 2^(k-3)*2.....+2*(k-2)+2^(0)*(k-1)  ===> 因为叶子层不用交换,所以i从 k-1 开始到 1;

        这个等式求解,我想高中已经会了:等式左右乘上2,然后和原来的等式相减,就变成了:

        S = 2^(k - 1) + 2^(k - 2) + 2^(k - 3) ..... + 2 - (k-1)

        除最后一项外,就是一个等比数列了,直接用求和公式:S = {  a1[ 1-  (q^n) ] }  / (1-q);

        S = 2^k -k -1;又因为k为完全二叉树的深度,所以 (2^k) <=  n < (2^k  -1 ),总之可以认为:k = logn (实际计算得到应该是 log(n+1) < k <= logn );

        综上所述得到:S = n - longn -1,所以时间复杂度为:O(n)


        更改堆元素后重建堆时间:O(nlogn)

第二步取出堆顶,不断的调整堆(转载于https://blog.csdn.net/loveliuzz/article/details/77618530

 更改堆元素后重建堆时间:O(nlogn)

        推算过程:

       1、循环  n -1 次,每次都是从根节点往下循环查找,所以每一次时间是 logn,总时间:logn(n-1) = nlogn  - logn ;

    

       综上所述:堆排序的时间复杂度为:O(nlogn)

 

稳定性分析:比如 1 2 3 4 4,大家可以自己调整,当取出堆顶元素时,两个4的相对位置会发生改变。

 

5.冒泡排序:每次遍历确定当前序列的最大值,知道所有元素都已完成遍历为止。

代码如下:

#include <iostream>

using namespace std;

int main()
{
    int s[]={10,9,8,7,6,5,4,3,2,1,12,11};
    for(int i=0;i<12;i++)
    {
        for(int j=0;j<12-i-1;j++)
        {
            if(s[j]>s[j+1])
            {
                int temp=s[j];
                s[j]=s[j+1];
                s[j+1]=temp;
            }
        }
    }
    for(int i=0;i<12;i++)
    {
        cout<<s[i]<<" ";
    }
    cout<<endl;
    return 0;
}

时间复杂度:如序列是有序的,那么每次都不用交换,此时时间复杂度为O(n);如果序列是逆序的,那么每次遍历都要逐个比较并交换,此时的时间复杂度为O(n*2)。

稳定性:分析比较过程可以知道,算法是稳定的。

 

6.快速排序:就是每次选择一个分割标准flag,小于flag的值放在左边,大于等于flag的值放在右边

代码如下:

#include <iostream>

using namespace std;

void quickSort(int * s,int i,int j)
{
    int end=j;
    if(i>j)
        return;
    int flag=s[i];
    while(i<j)
    {
        while(j>i&&s[j]>=flag)
            j--;
        if(j>i)
        {
            s[i]=s[j];
            i++;
        }
        while(i<j&&s[i]<flag)
            i++;
        if(j>i)
        {
            s[j]=s[i];
            j--;
        }
    }
    cout<<"i: "<<i<<" j: "<<j<<endl;
    s[i]=flag;

    cout<<"flag "<<flag<<":  ";
    for(int ii=0;ii<=9;ii++)
    {
        cout<<s[ii]<<" ";
    }
    cout<<endl;

    quickSort(s,0,i-1);
    quickSort(s,i+1,end);
}

int main()
{
    int s[]={10,9,8,7,6,1,2,3,4,5};
    quickSort(s,0,9);
    for(int i=0;i<10;i++)
    {
        cout<<s[i]<<" ";
    }
    cout<<endl;
    return 0;
}

时间复杂度:

如果每次划分都能将序列平分为两半,此时f(n)=2*f(n/2)+n=.....=(k^2)*f(n/(k^2)+(k-1)*n,此时n=k^2,得出k=logn,f(n)=n*f(1)+(logn-1)*n,故此时的时间复杂度为O(n*logn)。

如果每次划分都会产生一个空集时,此时f(n)=f(n-1)+n=...=f(1)+n*n,故此时的时间复杂度为O(n*2)

 

稳定性分析:

比如 4 2 4 3 5,第一次划分后为 3 2 4 4 5,此时两个4的相对位置发生了改变。

 

7.归并排序:归并排序分为两个部分,第一部分是不断的分割序列,第二部分就是不断的合并序列。

代码如下:

#include <iostream>

using namespace std;

void merge_fun(int * s,int i,int mid,int j,int * t)
{
    int k=0,s1=i,s2=mid+1;
    while(s1<=mid&&s2<=j)
    {
        if(s[s1]>s[s2])
        {
            t[k++]=s[s2];
            s2++;
        }
        else if(s[s1]<=s[s2])
        {
            t[k++]=s[s1];
            s1++;
        }
    }
    while(s1<=mid)
    {
        t[k++]=s[s1];
        s1++;
    }
    while(s2<=j)
    {
        t[k++]=s[s2];
        s2++;
    }

    for(int ss=0;ss<k;ss++)
    {
        s[ss+i]=t[ss];
    }
}

void merge_sort(int * s,int i,int j,int * t)
{
    if(i>=j)
        return;
    int mid=(i+j)/2;
    if(mid!=0)
        cout<<"mid: "<<mid<<" i:"<<i<<"  j:"<<j<<endl;
    merge_sort(s,i,mid,t);
    merge_sort(s,mid+1,j,t);
    merge_fun(s,i,mid,j,t);
}

int main()
{
    int s[]={10,9,8,7,6,1,2,3,4,5};
    int t[10];
    merge_sort(s,0,9,t);
    for(int i=0;i<10;i++)
    {
        cout<<t[i]<<" ";
    }
    cout<<endl;
    return 0;
}

和快排有点类似,总共会划分logn次,然后每一次都会归并,所以会比较n次,故时间复杂度为O(n*logn)。

 

8.基数排序:先找出序列中元素的最大位数,再从各位数开始,依次执行桶排序,从序列尾部开始回收元素。

代码如下:

#include <iostream>

using namespace std;

int get_oper_nums(int * s,int len)
{
    int reV=0;
    for(int i=0;i<len;i++)
    {
        int temp_s=s[i];
        int temp_rev=0;
        while(temp_s!=0)
        {
            temp_s=temp_s/10;
            temp_rev++;
        }
        if(temp_rev>reV)
        {
            reV=temp_rev;
        }
    }
    return reV;
}

void jishu_sort(int * s,int len)
{
    int oper_nums=get_oper_nums(s,len);
    int r=1;
    for(int i=0;i<oper_nums;i++)
    {
        int count[10];
        for(int i=0;i<10;i++)
            count[i]=0;

        for(int i=0;i<len;i++)
        {
            int temp=s[i]/r;
            int j=temp%10;
            count[j]++;
        }

        for(int i=1;i<10;i++)
        {
            count[i]+=count[i-1];
        }

        int temp_s[len];
        for(int i=len-1;i>=0;i--)
        {
            int temp=s[i]/r;
            int j=temp%10;
            temp_s[count[j]-1]=s[i];
            count[j]--;
        }

        for(int i=0;i<len;i++)
        {
            s[i]=temp_s[i];
        }
        r=r*10;

        for(int i=0;i<12;i++)
        {
            cout<<s[i]<<" ";
        }
        cout<<endl;
    }
}

int main()
{
    int s[]={10,9,8,7,6,5,4,3,2,1,12,11};
    jishu_sort(s,12);
    for(int i=0;i<12;i++)
    {
        cout<<s[i]<<" ";
    }
    cout<<endl;
    return 0;
}

时间复杂度分析:

d表示最大位数,r表示基数,n表示要排序的元素个数,则时间复杂度为O(d*(r+n));

posted @ 2019-08-29 11:44 张杨 阅读(...) 评论(...) 编辑 收藏