【数据结构】各种排序算法

排序算法稳定性

如果在元素序列中有两个元素R[i]和R[j],它们的排序码K[i] == k[j],且 在排序之前,元素R[i]在R[j]的前面。如果在排序之后,元素R[i]仍在R[j] 之前,则称这个排序算法是稳定的,否则称这个排序算法是不稳定的。


内部排序:数据元素全部放在内存中的排序。  

外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能 在内外存之间移动数据的排序

【数据结构】各种排序算法_第1张图片

一.插入排序

直接插入排序:

适用场景:数据元素接近有序,数据量少,时间复杂度O(n^2)   空间复杂度O(1)  稳定

思想:默认第一个元素前的元素都已经有序,将之后的元素全部插入到前面

插入方法:标记待插数,与前面的元素进行比较,找到插入的位置,将要插入的位置的元素全向后搬移,腾出位置插入待插数,一次循环直到有序

void InsretSort(int *arr,int size)
{
	
	int i=1;
	int key=0;
	int end=0;
	for(i=1;i=0 && key

优化:因为上面的方法中,主要就是找位置和搬移元素,但是每次找位置都是在有序数据中找,所以这里可以将找位置与搬移元素分开,用二分查找找位置,代码如下:

void InsretSort(int *arr,int size)
 {
	int i=1;
	int end=0;
	int key=0;
	int left=0;
	int right=0;
	for(i=1;i

希尔排序:

基本思想:又称缩小增量排序,是对直接插入排序的优化,当数据量非常大时,因为插入排序的特性,处理起来会非常麻烦,这里我们可以将数据按照一定的间隔进行分组,然后分别对每组用插入排序的思想进行排序,然后减小间间隔,再继续,直到间隔为1

void ShellSort(int *arr,int size)
{
	int i=0;
	int gap=size;
	while(gap>1)
	{
		gap=gap/3+1;
		for(i=gap;i=0 && arr[end]>key)
			{
				arr[end+gap]=arr[end];
				end-=gap;
			}

			arr[end+gap]=key;
		}
	}
}

时间复杂度:O(n^1.25  ~   1.6n^1.25)

适用场景:应用于数据量大

稳定性:不稳定,区间插入

二.选择排序

直接选择排序

基本思想:每一趟(第i趟,i=0,1,...,n-2)在后面(前面)n-i个待排序的数据元素集合中选出关键码最小(最大的元素),待到第n-2趟做完,待排序远视眼集合中只剩下一个元素,排序结束

void Swap(int *left,int *right)
{
	int tmp=*left;
	*left=*right;
	*right=tmp;
}

void SelectSort(int *arr,int size)
{
	int i=0;
	for(i=0;i

时间复杂度:O(n^2)

稳定性:不稳定

堆排序

基本思想:因为大堆或者小堆,堆顶元素一定是最大值或最小值,所以可以将堆顶元素与最后一个元素交换,然后删除最后一个元素,在调整使之为堆。升序--大堆  降序--小堆

void _Adjust_down(int *arr,int parent,int size)
{
	int child=parent*2+1;//默认左孩子为最大值
	while(child>1;
	int end=size-1;
	for(int i=root;i>=0;i--)
		_Adjust_down(arr,i,size);
	while(end)
	{
		Swap(arr,arr+end);
		_Adjust_down(arr,0,end);
		end--;
	}
}

时间复杂度:O(nlog2^n)

稳定性;不稳定

三.交换排序

冒泡排序

void BubbleSort(int *arr,int size)
{
	int i=0;
	for(i=0;iarr[j+1])
			{
				flag=1;
				Swap(arr+j,arr+j+1);
			}
		}
		if(flag==0)
			break ;
	}
}

时间复杂度:O(n^2)

稳定性:稳定

快速排序

基本思想:任取待排序元素序列中的某元素作为基准值,按照该排序码将排序集合分割成两子序列,左子序列中的所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止

交换法:两个索引,一个找比基准值大的,一个从后找比基准值小的,最后交换,停下来的位置,就是基准值应该放的位置

int GetMiddleData(int *arr, int left, int right)
{
    int mid = left + (right - left) / 2;
    if (arr[left] < arr[right])
    {
        if (arr[mid] < arr[left])
            return left;
        else if (arr[mid] > arr[right])
            return right;
        else
            return mid;
    }

    else
    {
        if (arr[mid] < arr[right])
            return right;
        else if (arr[mid] > arr[left])
            return left;
        else
            return mid;
    }
}

int Partion1(int *arr, int left, int right)
{
    int begin = left;
    int end = right - 1;
    int key = 0;
    int midIdx = GetMiddleData(arr, left, end);
    if (midIdx != end)
        Swap(arr + midIdx, arr + end);

    //这里给出的基准值每次都取区间的最后一个元素
    key = arr[end];

    while (begin < end)
    {
        //从前往后找比基准值大的元素
        while (begin < end && arr[begin] <= key)
            ++begin;

        //从后往前找比基准值小的元素
        while (begin < end && arr[end] >= key)
            --end;

        //比基准值小的放到前面,比基准值大的放到后面(升序)
        if (begin < end)
            Swap(arr + begin, arr + end);
    }

    //出了循环,begin这个位置如果不是在最后,就将最后一个元素(基准值)和begin上的
    //元素交换,这样,begin之前的元素都小于基准值,begin之后的元素都大于基准值
    if (begin != right -1)
        Swap(arr + begin, arr + right -1);

    return begin;
}

void _QuickSort(int *arr, int left, int right)
{
    //只有一个数据一定有序
    if (right - left > 1)
    {
        int div = Partion1(arr, left, right);
        _QuickSort(arr, left, div);
        _QuickSort(arr, div + 1, right);
    }
}


void QuickSort(int *arr, int size)
{
    //区间为左闭右开
    _QuickSort(arr, 0, size);
}

时间复杂度:O(n^2)

适用场景:数据比较随机

稳定性:不稳定

四.归并排序

基本思想:将待排序的元素序列分成两个长度相等的子序列,对每个子序列排序然后将他们合并,合并两个子序列的过程称为二路合并

void MergeData(int *arr, int left, int mid, int right, int* tmp)
{
    //区间为左闭右开
    int beginL = left, endL = mid;
    int beginR = mid, endR = right;
    int index = left;//用于索引辅助空间

    while (beginL < endL && beginR < endR)
    {
        if (arr[beginL] <= arr[beginR])
            tmp[index++] = arr[beginL++];
        else
            tmp[index++] = arr[beginR++];
    }

    //左区间没有搬移完
    while (beginL < endL)
        tmp[index++] = arr[beginL++];

    //右区间没有搬移完
    while (beginR < endR)
        tmp[index++] = arr[beginR++];
}

void _MergeSort(int *arr, int left, int right, int *tmp)
{
    if (left+1 < right)
    {
        //每次进来都对数据进行平均切分区间,因为归并必须是两组有序的
        //数据,所以,切分到只剩一个数据时才是有序,才能进行归并.
        int mid = left + (right - left) / 2;

        //数据均分为两个区间(左闭右开),左区间和右区间,先排左区间
        _MergeSort(arr, left, mid, tmp);

        //排右区间
        _MergeSort(arr, mid, right, tmp);

        //走到这里说明左右两个区间都已经有序,在进行归并
        MergeData(arr, left, mid, right, tmp);

        //将辅助空间已经排好序的数据拷贝到arr
        memcpy(arr + left, tmp + left, (right - left)*sizeof(arr[0]));

    }
}

void MergeSort(int *arr, int size)
{
    int *tmp = (int*)malloc(sizeof(int)*size);
    if (NULL == tmp)
        return;

    //区间为左闭右开
    _MergeSort(arr, 0, size, tmp);

    free(tmp);
}

时间复杂度:O(nlog2^n)     空间复杂度:O(n)

适用场景:数据量大的情况

稳定性:稳定

五.计数排序

基本思想:计数排序又称鸽巢原理,是对哈希直接定制法的变形应用,先统计相同元素出现次数,再根据统计的结果将序列回收到原来的序列中

void CountSort(int *arr, int size)
{
    int range = 0;//用来标识这组数据的范围
    int index = 0;
    int MaxValue = arr[0];//用来标识数据的最大值
    int MinValue = arr[0];
    int i = 0;
    int *count = NULL;//用来计数的空间

    for (i = 1; i < size; i++)
    {
        if (arr[i] > MaxValue)
            MaxValue = arr[i];

        if (arr[i] < MinValue)
            MinValue = arr[i];
    }

    //计算范围,这里一定要+1
    range = MaxValue - MinValue + 1;

    //这里用calloc是为了将空间元素初始化为0
    count = (int*)calloc(range, sizeof(arr[0]));
    if (NULL == count)
        return;

    //统计每个元素出现次数
    for (i = 0; i < size; i++)
    {
        //arr[i]-MinValue为count数组的下标
        count[arr[i] - MinValue]++;
    }

    //回收元素
    for (i = 0; i < range; i++)
    {
        //只要count[i]不为0,说明里面还有i+MinValue这个元素
        while (count[i]--)
            arr[index++] = i + MinValue;
    }

    free(count);
}

时间复杂度:O(n)

适用场景:适用于数据比较密集集中于某个范围

稳定性:稳定

你可能感兴趣的:(【数据结构】各种排序算法)