排序思想,代码实现和优缺点比较

19 篇文章 0 订阅
18 篇文章 0 订阅

###插入排序

void insert_sort(int *a, int size)
{
	//插入排序:
	//每一步将一个待排序的元素,插入到前面已经有序的一组元素中,
	//直到元素排完为止。
	assert(a);
	assert(size>0);
	for (int i = 1;i < size;i++)
	{
		int tmp = a[i];
		int n = i;
		while (n)
		{
			if (tmp < a[n - 1])
			{
				a[n] = a[n - 1];
				n--;
			}
			else
				break;
		}
		a[n] = tmp;

	}
}



###希尔排序

void shell_sort(int *a, int size, int interval)
{
	//希尔排序就是插入排序的优化版,分为2步:
	//1.预排序:取一个值x作为间隔数,对应间隔为x的值比较大小,使数组基本有序
	//2.插入排序(此时进行的插入排序的时间复杂度不再是O(n^2),因为已经基本有序)

	assert(a);
	assert(size > 0);
	for (int i = 0;i < interval;i++)
	{
		if (a[i] > a[i + interval])
		{
			swap(&a[i], &a[i + interval]);
		}
	}

	insert_sort(a, size);

}

###选择排序

void select_sort(int *a, int size)
{
	//选择排序
	//思想:每趟在后面的n-i(i为趟数)选一个最小的值,与第i个值交换,直至数组中只剩一个元素。
	//时间复杂度:始终为O(n^2),所以不是一个很优的排序算法。
	assert(a&&size > 0);

	for (int i = 0;i<size - 1;i++)
	{
		int *min = &a[i];
		int j = i;
		while (j < size - 1)
		{
			if (*min > a[j + 1])
			{
				min = &a[j + 1];
			}
			j++;
		}
		if (min != &a[i])
		{
			swap(min, &a[i]);
		}
	}
}

###堆排序

void heapadjustdown(int *a, int size, int parent)
{
	//向下调整算法
	assert(a&&size > 0);
	int child = parent * 2 + 1;
	while (child < size)
	{
		if (child + 1 < size&&a[child + 1] > a[child])
		{
			child++;
		}
		if (a[parent] < a[child])
		{
			swap(&a[parent], &a[child]);
		}
		parent = child;
		child = parent * 2 + 1;
	}
}
void heap_sort(int *a, int size)
{
	//堆排序
	//思想:升序:建大堆;降序:建小堆
	//建好堆后,将堆顶元素与最后一个元素进行交换,堆的大小-1,对堆顶元素进行向下调整,直至堆内只有一个元素
	assert(a&&size > 0);
	int parent = ((size - 1) - 1) / 2;
	int child = parent * 2 + 1;
	int len = size;
	while (parent >= 0)
	{
		heapadjustdown(a, size, parent);
		parent--;
	}
		//建好堆了
	while (len > 1)
	{
		swap(&a[0], &a[len - 1]);
		len--;
		heapadjustdown(a, len, 0);
	}
	
}

###冒泡排序


void bubble_sort(int *a, int size)
{
	//冒泡排序
	assert(a&&size > 0);
	for (int i = 0;i < size;i++)
	{
		int flag = 1;
		for (int j = 0;j < size - i - 1;j++)
		{
			if (a[j] > a[j + 1])
			{
				swap(&a[j], &a[j + 1]);
				flag = 0;
			}
		}
		if (flag == 1)
		{
			break;
		}
	}
}

这里写图片描述
###快速排序其一(左右指针法,hoare法)

int GetMidIndex(int *a, int left, int right)
{
	int mid = left + (right - left) / 2;//取到中间位置的下标
	if (a[left] < a[mid])
	{
		if (a[right] < a[left])
		{
			return left;
		}
		if (a[right] > a[mid])
		{
			return mid;
		}
		else
		{
			return right;
		}
	}
	else
	{
		if (a[right] > a[left])
		{
			return left;
		}
		if (a[right] < a[mid])
		{
			return mid;
		}
		else
			return right;
	}
}



int quick_sort1(int *a, int left, int right)
//左右指针法(hoare版本)
//单趟快排达到的效果是该数放在它排好的位置上(左边的数都比它小,右边的数都比它大)
//再对其进行左右子排序,直至整个序列都有序
{
	assert(a);
	
	int begin = left;
	int end = right;
	int key = GetMidIndex(a, left, right);//三数取中法,能减少排序次数。
	swap(&a[key], &a[right]);//方便起见,把中位数放到最右边。
	while (begin < end)
	{
		while (begin < end&&a[begin] <= a[right])
		{
			begin++;
		}
		while (begin < end&&a[end] >= a[right])
		{
			end--;
		}
		if (begin < end)
		{
			swap(&a[begin], &a[end]);
		}
	}
	swap(&a[begin], &a[right]);//将中位数放到正确的位置,至此该数不用在做变化
	print_arr(a, 10);
	printf("\n");
	return begin;//返回最终放置中位数的下标,为后序递归确定边界
}




void quick_sort(int *a, int left, int right)
{
	assert(a);
	if (left >= right)
	{
		return;
	}
	int mid = quick_sort1(a, left, right);
	quick_sort(a, left, mid - 1);
	quick_sort(a, mid + 1, right);
}

###快速排序其二(挖坑法)
这里写图片描述
开始时,7为坑值key,begin向右找,找到9比7大,用9填7处的坑,自己成为新的坑,end向左找,找到1比7小,则用1填9的坑,自己成为新坑,如此重复直至begin==end,将key值7填入begin出,算作一次排序。

int quicksort2(int *a, int left, int right)
{
	assert(a);
	int begin = left;
	int end = right;
	int key = a[right];
	while (begin < end)
	{
		while (begin<end && a[begin] <= key)
		{
			begin++;
		}
		a[end] = a[begin];
		while (begin<end && a[end] >= key)
		{
			end--;
		}
		a[begin] = a[end];
	}
	a[begin] = key;
	return begin;
}
void quick_sort(int *a, int left, int right)
{
	assert(a);
	if (left >= right)
	{
		return;
	}
	int mid = quicksort2(a, left, right);
	quick_sort(a, left, mid - 1);
	quick_sort(a, mid + 1, right);
}

###快速排序其三(前后指针法)

int quicksort3(int *a, int left, int right)
{
	//定义两个指针,prev和cur,再定义key的值,cur指针从left开始,
	//遇到比key大的就过滤掉,比key小的,就停下来,prev++,
	//判断prev和cur是否相等,如果不相等,就将两个值进行交换
	assert(a);
	int cur = left;
	int prev=left-1;
	int key = GetMidIndex(a, left, right);
	swap(&a[key], &a[right]);
	while (cur != right)
	{
		while (a[cur] < a[right] && (++prev)!=cur)
		{
			swap(&a[prev], &a[cur]);

		}
		cur++;
		
	}
	swap(&a[++prev], &a[right]);
	return prev;
}
void quick_sort(int *a, int left, int right)
{
	assert(a);
	if (left >= right)
	{
		return;
	}
	int mid = quicksort3(a, left, right);
	quick_sort(a, left, mid - 1);
	quick_sort(a, mid + 1, right);
}

###快速排序之非递归

void QuickSortNonR(int *a, int left, int right)//快排非递归
{
    assert(a);
    if (left >= right)
        return;
    Stack st;
    StackInit(&st);
    int topl;//栈顶的左
    int topr;//栈顶的右
    int div = 0;
    //进栈顺序:先左后右
    StackPush(&st, left);
    StackPush(&st, right);
    while (StackEmpty(&st))
    {
        //出栈先右后左
        topr = StackTop(&st);
        StackPop(&st);
        topl = StackTop(&st);
        StackPop(&st);
        div = partsort1(a, topl, topr);
        // topr  div-1 div  div+1 topr
        //div的左边区间先入栈,右边区间后入栈,但是栈先进后出,先排右边区间再排左边区间
        if (topl < div - 1)
        {
            StackPush(&st, topl);
            StackPush(&st, div - 1);
        }
        if (div + 1 < topr)
        {
            StackPush(&st, div + 1);
            StackPush(&st, topr);
        }
    }
}

快速排序在数据较多时具有很大优势,但数据少时却没有之前的几种方法便捷,比如,要排5个数,用左右指针法,要递归调用7次quicksort,此时就不如插入排序来的方便了,所以为提高效率,我们可以做一下优化:当数据量小于某数值时(比如20),就不用快速排序,转而使用插入排序

if(right-left+1<20)
{
	insert_sort(a+left , right-left+1);
}
else
{
	.......
}

###归并排序
归并排序又称外排序,可以在磁盘中进行排序,不用读入内存

void _MergeSort(int *a, int left, int right, int *tmp)
{
    if (left >= right)
        return;
    if (right - left + 1 < 20)
    {
        InsertSort(a + left, right - left + 1);//小区间优化:当数据个数(闭区间需要加1)小于20时,直接插入排序
        return;
    }
    int mid = left + (right - left) / 2;
    _MergeSort(a, left, mid, tmp);//将左边划分为有序
    _MergeSort(a, mid + 1, right, tmp);//将右边划分语序
    int begin1 = left, end1 = mid;
    int begin2 = mid + 1, end2 = right;
    int index = left;
    // begin1--end1是有序区间   begin2--end2是有序区间
    //将两段有序区间合并为一段有序区间
    while (begin1 <= end1 && begin2 <= end2)
    {
        //把小的数据放在tmp中
        if (a[begin1] <= a[begin2])//等于号保证归并是稳定的
        {
            tmp[index] = a[begin1];
            index++;
            begin1++;
        }
        else
        {
            tmp[index] = a[begin2];
            index++;
            begin2++;
        }
    }
    if (begin1 > end1)//说明begin2-end2还有数据
    {
        while (begin2 <= end2)
            tmp[index++] = a[begin2++];
    }
    else //说明begin1 - end1还有数据
    {
        while (begin1 <= end1)
            tmp[index++] = a[begin1++];
    }
    index = left;
    while (index <= right)//由于tmp只是个临时数组,需要将有序数据重新放到数组a中
    {
        a[index] = tmp[index];
        index++;
    }
}
void MergeSort(int *a, int n)
{
    assert(a); 
    int *tmp = (int *)malloc(sizeof(int)*n);
    _MergeSort(a, 0, n - 1, tmp);//tmp是临时数组
    free(tmp);
    tmp = NULL;
}

排序性能比较:
1.选择排序和插入排序:插入排序较好
两者时间复杂度都为0(n^2),但对于有序的插入排序时间复杂度为0(n),
直接插入在有序区间后面,不用再挪动数据,选择排序依然是0(n^2);

2.冒泡排序、插入排序、选择排序:插入排序较好
如 1 2 3 5 4
插入排序是5次可以有序,而冒泡在4和5交换有序后还需要再判断是否有序,才结束排序,即7次

3.经过测试,大量数据快排性能优于堆排
各种排序稳定性:
稳定性指两个相同的值在排序后相对位置不变。
插入排序:稳定(相等就插在后面)
希尔排序:不稳定(有gap,跳跃着排序,可能回影响相对位置)
选择排序:不稳定(3 5 9* 9 2 7—>2 5 7 9 3 9*)
冒泡排序:稳定(两个数据相等,不交换)
堆排:不稳定(5 4 5*,第一个5和最后一个5交换,相对位置发生变化)
快速排序:不稳定( 6 7 1 5
5—>1 5 6 5* 7)
归并排序:稳定(相等就将前一个数据放入tmp数组)
这里写图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值