排序算法(c语言实现)

作为一个程序猿,编写程序,排序算法算是最基本的了。这篇博客中,我尽量以最通俗易懂的语言,最简单的代码来讲清楚这些比较常见的排序算法。

好了,首先来看一下各种排序算法的效率和稳定性方面的总结(这张图很好,网上找的):


下面我就根据这张图来说明各种排序算法。

一.插入排序

1.直接插入排序

直接插入排序就是把后面的元素依次插入到前面的有序列中,(第一次以第一个元素为有序列),插入位置后面的元素后移。下面是代码.

#include<stdio.h>

int main()
{
    int sqList[]= {1,3,6,7,2,4,4,0,9,8};
    insertSortDirect(sqList,10);
    printArr(sqList, 10);

    return 0;
}

void insertSortDirect(int *sqList, int length)
{
    int i;
    for(i = 1; i < length; i++)//第一次把下标为0的作为有序序列
    {
        int temp = sqList[i];

        int j;
        for(j = i; j > 0; j--)
        {
            if(sqList[j - 1] > temp)
            {
                 sqList[j] = sqList[j - 1];
            }
            else
                break;
        }

        sqList[j] = temp;
    }
}

void printArr(int *sqList, int length)
{
    int i;
    for(i = 0; i < length; i++)
    {
        printf("%d ", sqList[i]);
    }
}

2.shell排序

shell排序其实就是直接插入排序的扩展。举个例子,shell排序先将待排数组以3为间隔分为几个子数组,对这几个子数组进行直接插入排序,然后以2为间隔重复,最后以1为间隔重复(此时就是直接插入排序)。因为对基本有序的数组进行直接插入排序效率很高。下面是代码。

#include<stdlib.h>

void shellInsert(int *sqList, int length, int increment)
{
    int i;
    for(i = increment; i < length; i++)
    {
        int temp = sqList[i];
        int j;
        for(j = i; j >= increment; j = j - increment)//不是把愿数组真的分为几个子数组,因为每一次的shellInsert的步长是固定的,所以可以这样
        {
            if(temp < sqList[j - increment])
            {
                sqList[j] = sqList[j - increment];
            }
            else
                break;
        }
        sqList[j] = temp;
    }
}

void shellSort(int *sqList, int length1, int *dlta, int length2)
{
    int i;
    for(i = 0; i < length2; i++)
    {
        shellInsert(sqList, length1, dlta[i]);
    }
}

void printArr(int *sqList, int length)
{
    int i;
    for(i = 0; i < length; i++)
    {
        printf("%d ", sqList[i]);
    }
}


int main()
{
    int sqList[]= {1,3,6,7,2,4,4,0,9,8};
    int dlta[] = {3, 2, 1};
    shellSort(sqList, 10, dlta, 3);
    printArr(sqList, 10);
    return 0;
}

扩展几个:

1.折半插入排序

在直接插入排序时,用二分查找法来寻找待插入的位置。

#include <stdio.h>

void insertSortB(int *sqList, int length)
{
    int i;
    for(i = 1; i < length; i++)
    {
        int temp = sqList[i];
        int max = i - 1;
        int min = 0;
        while(min <= max)
        {
            int center = (max + min)/2;
            if( temp < sqList[center])
                max = center - 1;
            else
                min = center + 1;
        }

        int j;
        for(j = i - 1; j >= max + 1; j--)
        {
            sqList[j + 1] = sqList[j];
        }
        sqList[max + 1] = temp;
    }
}

void printArr(int *sqList, int length)
{
    int i;
    for(i = 0; i < length; i++)
    {
        printf("%d ", sqList[i]);
    }
}


int main()
{
    int sqList[]= {1,3,6,7,2,4,4,0,9,8};
    insertSortB(sqList, 10);
    printArr(sqList, 10);
    return 0;
}

2.2-路插入排序

3.表插入排序

二.选择排序

1.直接选择排序

直接选择排序就是每次寻找最大(小)的元素位置,然后将此元素与紧跟在有序列后面的第一个元素交换。直到有序列等于数组长度。虽然有交换,但是是基于选择的。

#include<stdlib.h>

void printArr(int *sqList, int length)
{
    int i;
    for(i = 0; i < length; i++)
    {
        printf("%d ", sqList[i]);
    }
}

int selectMin(int *sqList, int low, int  high)
{
    int min = sqList[low];
    int minLocation = low;

    int i;
    for(i = low; i < high; i++)
    {
        if(sqList[i] < min)
        {
            min = sqList[i];
            minLocation = i;
        }
    }

    return minLocation;
}

void simpleSelectSort(int *sqList, int length)
{
    int i;
    for(i = 0; i < length; i++)
    {
        int temp = sqList[i];
        int minLocation = selectMin(sqList, i, length);
        sqList[i] = sqList[minLocation];
        sqList[minLocation] = temp;
    }
}

int main()
{
    int sqList[]= {1,3,6,7,2,4,4,0,9,8};
    simpleSelectSort(sqList, 10);
    printArr(sqList, 10);
    return 0;
}


2.堆排序

堆排序也很简单,原理很容易懂,主要是循环的结束判断用到了完全二叉树的性质。其实二叉树的性质大家也不用死记硬背,用的时候画一颗树推一下就好了。下面是代码,很容易看懂。

#include<stdio.h>

void print(int *sqList, int length)
{
    int i;
    for(i = 0; i < length; i++)
    {
        printf("%d ", sqList[i]);
    }
}

//m为调整的开始点,n为m堆得节点的最大下标
void heapAdjust(int *sqList, int i, int n)
{
    int index = i;
    int temp = sqList[i];

    i = 2 * i + 1;
    while(i <= n)
    {
        if(i + 1 <= n && sqList[i] < sqList[i + 1])
            i++;
        if(temp >= sqList[i])
            break;

        sqList[index] = sqList[i];
        sqList[i] = temp;
        index = i;
        temp = sqList[i];

        i = 2 * i + 1;
    }
}

//从倒数第二层的第一个元素开始依次向上,对每个节点进行调整
//            0
//          /    \
//         1      2
//       /   \   /  \
//      3     4  5   6
//     /
//     7
//所以构造最大堆时,调整函数的参数n可以一直是数组长度
void creatHeap(int *sqList, int length)
{
    int i;
    for(i = (length - 1) / 2; i >= 0; i--)
        heapAdjust(sqList, i, length - 1);
}

int main()
{
    int sqList[] = {65, 0, 78, 18, 4, 66, 97};
    creatHeap(sqList, 7);

    int temp = sqList[0];
    sqList[0] = sqList[6];
    sqList[6] = temp;

    int i;
    for(i = 5; i >= 0; i--)
    {
        heapAdjust(sqList, 0, i);
        temp = sqList[0];
        sqList[0] = sqList[i];
        sqList[i] = temp;
    }

    print(sqList, 7);
}


三.交换排序

1.冒泡排序

每趟冒泡的过程,就是不断交换相邻元素,以此把最大(小)的元素交换到有序列后面。

#include<stdlib.h>

void printArr(int *sqList, int length)
{
    int i;
    for(i = 0; i < length; i++)
    {
        printf("%d ", sqList[i]);
    }
}

void swap(int *sqList, int i, int j)
{
    sqList[i] = sqList[i] + sqList[j];
    sqList[j] = sqList[i] - sqList[j];
    sqList[i] = sqList[i] - sqList[j];
}

void bubbleSort(int *sqList, int length)
{
    int i;
    for(i = 0; i < length; i++)
    {
        int j;
        for(j = 0; j < length - i -1; j++)
        {
            if(sqList[j] > sqList[j + 1])
                swap(sqList, j, j + 1);
        }
    }
}

int main()
{
    int sqList[]= {1,3,6,7,2,4,4,0,9,8};
    bubbleSort(sqList, 10);
    printArr(sqList, 10);
    return 0;
}



2.快速排序

快速排序就是每次选择一个元素,然后把比他大的放前面,比他小的放后面,他放中间。递归(或循环)直到子数组只有一个元素。

#include<stdlib.h>

void printArr(int *sqList, int length)
{
    int i;
    for(i = 0; i < length; i++)
    {
        printf("%d ", sqList[i]);
    }
}


int partition(int *sqList, int low, int high)
{
    int temp = sqList[low];

    while(low < high)
    {
        //第一个判断是边界的检验
        while(low < high && sqList[high] >= temp)  high--;
        sqList[low] = sqList[high];
        while(low < high && sqList[low] <= temp)    low++;
        sqList[high] = sqList[low];
    }
    sqList[low] = temp;

    return low;
}

void quickSort(int *sqList, int low, int high)
{
    if(low < high)
    {
        int mid = partition(sqList, low, high);
        quickSort(sqList, low, mid -1);
        quickSort(sqList, mid + 1, high);
    }
}

int main()
{
    int sqList[]= {1,3,6,7,2,4,4,0,9,8};
    quickSort(sqList, 0, 9);
    printArr(sqList, 10);
    return 0;
}



四.归并排序

归并排序是分治法应用的很好例子。所谓分治法,就是把原问题分为规模较小,问题一样的子问题,不断分割知道子问题很容易就解出,记录子问题的结果,然后合并为原问题的解。下面是代码,很容易就可以看懂其中的过程。

#include<stdio.h>

void print(int *sqList, int length)
{
    int i;
    for(i = 0; i < length; i++)
    {
        printf("%d ", sqList[i]);
    }
}

void merge(int *sqList, int first, int mid, int last, int *temp)
{
    int m = first;
    int i = first;
    int j = mid + 1;
    while(i <= mid && j <= last)
    {
        if(sqList[i] < sqList[j])
        {
            temp[m] = sqList[i];
            i++;
            m++;
        }
        else
        {
            temp[m] = sqList[j];
            j++;
            m++;
        }
    }

    if(i <= mid)
    {
        for(i; i <= mid; i++)
        {
            temp[m] = sqList[i];
            m++;
        }
    }


    if(j <= last);
    {
        for(j; j <= last; j++)
        {
            temp[m] = sqList[j];
            m++;
        }
    }

    for(i = first; i <= last; i++)
        sqList[i] = temp[i];

}

void mergingSort(int *sqList, int begin, int end, int *temp)
{
    if(begin == end)
        temp[begin] = sqList[begin];
    else
    {
        int i = (begin + end) / 2;
        mergingSort(sqList, begin, i, temp);
        mergingSort(sqList, i + 1, end, temp);
        merge(sqList, begin, i, end, temp);
    }

}

int main()
{
    int sqList[] = {65, 70, 78, 18, 19, 66, 97};
    int temp[7] = {};
    mergingSort(sqList, 0, 6, temp);
    print(sqList, 7);
}

五.基数排序

基数排序是基于多关键字的排序,k0,k1,k2,k3....kn,(一个关键字可以拆分为几个关键字,比如对数字排序,个十百可以分别作为关键字进行基数排序)。

基数排序可以分为两种:

1.从主关键字开始,即MSD基数排序

这种方法根据关键字的主次顺序依次把数组分为子数组,等到拆分到只剩最次关键字时,排序即可。

2.从最次关键字开始,即LSD基数排序。但对k i(0 <= i <= n - 1)排序时,只能用稳定的排序算法。

这种方法是从最次关键字开始,进行若干次分配,收集。下面以例子进行说明。

#include<stdio.h>
/**
*这里用数组模拟链表
*当然也可以使用真实的链表
*/
/**
*对3位整数进行排序,则有三个关键字:个十百。
*每个关键字的基数都是0-9,所以有3次分配收集的过程。
*每一次分配,需要有一个包含10个元素的数组,用来记录每个基数对应的链表(逻辑上的)的头元素的开始下标
*/



//求出数字的个十或百位的数字作为关键字
//location取值为1,2,3.分别对应个十百
int getKey(int data, int location)
{
    int key;

    int i;
    for(i = 0; i < location; i++)
    {
        key = data % 10;
        data = data / 10;
    }

    return key;
}

typedef struct
{
    //因为data里包含了关键字数组,所以不用关键字数组了。
    int data;
    int next;
}SLCell;

//分配的算法
void distribute(SLCell *sqList, int length, int *baseBegin, int *baseEnd, int keyLocation)
{
    int key;//key其实也是这个数在base数组中的下标

    int i;
    for(i = 0; i < length; i++)
    {
        key = getKey(sqList[i].data, keyLocation);
        if(baseBegin[key] == -1)
        {
            baseBegin[key] = i;
            baseEnd[key] = i;
        }
        else
        {
            sqList[baseEnd[key]].next = i;
            baseEnd[key] = i;
        }
    }
}

//收集的算法
void collect(SLCell *sqList, int length, int *baseBegin)
{
    int index = 0;
    SLCell sqCell;
    SLCell sqListTemp[1000];//c不能声明为length长度

    int i;
    for(i = 0; i < 10; i++)
    {
        if(baseBegin[i] != -1)
        {
            int flag = 1;
            sqCell = sqList[baseBegin[i]];

            baseBegin[i] = -1;//为下一次重新分配做好准备(还原)
            while(flag == 1)
            {
                sqListTemp[index].data = sqCell.data;
                sqListTemp[index].next = -1;baseBegin[i]
                index++;

                if(sqCell.next == -1)
                    flag = 0;
                else
                    sqCell = sqList[sqCell.next];
            }
        }

    }

    for(i = 0; i < length; i++)
    {
        sqList[i].data = sqListTemp[i].data;
        sqList[i].next = -1;
    }
}

int main()
{
    /
    //构造一个待排序的链表
    SLCell sqList[11] = {};
    int datas[] = {789, 123, 780, 123, 345, 321, 124, 98, 567 , 190,111};
    int i;
    for(i = 0; i < 11; i++)
    {
        sqList[i].data = datas[i];
        sqList[i].next = -1;
    }

    int baseBegin[10] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1};
    int baseEnd[10] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1};

    for(i = 1; i <= 3; i++)
    {
        distribute(sqList, 11, baseBegin, baseEnd, i);
        collect(sqList, 11, baseBegin);
    }

    for(i = 0; i < 11; i++)
    {
        printf("%d ", sqList[i]);
    }

}




















































































  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值