数据结构——排序

目录

1.冒泡排序

2.插入排序

3.希尔排序

4.选择排序

5.堆排序

6.快速排序

7.归并排序


1.冒泡排序

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

空间复杂度:O(1)。

稳定性:稳定。

冒泡排序的思路是:通过从第一个元素开始交换,依次找到最大值,次大值,直到有序(针对升序,降序相反)。

代码(升序):

void BubbleSort(int* a, int n)
{
	for (int j=0;j<n-1;j++)
	{
        int flag=1;
		for (int i = 0; i < n - 1-j; i++)
		{
			if (a[i] > a[i + 1])
			{
                flag=0;
				int tmp=a[i];
                a[i]=a[i+1];
                a[i+1]=tmp;
			}
		}
        if(flag==1)
        {
            break;
        }
	}
}

第一个for循环代表总趟数,第二个for循环表示每一趟的具体比较,如果当前元素大于下一个元素则就交换,flag判断该趟是否有元素交换,如果没有,证明数组已有序,直接跳出循环。

注意点:

1.因为每次要比较当前元素与下一个元素,所以遍历总趟数要为n-1,这是图中j<n-1,i<n-1-j中n减去1的原因。

2.每一趟走完后,都会使一个数到达正确的位置,并且是从后向前的,因此在上一趟排完后,该趟遍历的数应该去掉最后一个,即上一趟到达正确位置的数,当j=0时,总遍历数为n-1,当j=1时,最后一个元素不必遍历,故总遍历数为n-1-1,当j=2时,最后两个元素不必遍历,故总遍历数为n-1-2,以此类推,每一趟的遍历数为n-1-j。

2.插入排序

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

空间复杂度:O(1)。

稳定性:稳定。

插入排序的思想(升序):记录当前数并与前面的数比较,如果比前一个小那么前一个数往后移,否则就插入到当前比较数的后面。

代码:

void insertsort(int* a,int n)
{
	for (int i=0;i<n-1;i++)
	{
		int end = i;
		int key = a[end + 1];
		while (end>=0)
		{
			if (a[end] > key)
			{
				a[end + 1] = a[end];
			}
			else
			{
				break;
			}
			end--;
		}
		a[end + 1] = key;
	}
}

这里我们选取一个结尾end从0开始,end+1得到一个当前数key,从end开始往前与key比较,比key大的往后移,不然就在end+1处(即当前比较数的后面)填入key。

注意点:end可能取到-1(即key在已比较的数中最小),因此在while循环外填入key。(也可在里面填入,但这样必须对end=-1特殊处理)。

3.希尔排序

时间复杂度:O(nlogn)。

空间复杂度:O(1)。

稳定性:不稳定。

试想如果插入排序接近有序,那么每次只需比较几次就能达到有序,大大减少了比较次数。

希尔排序思路(升序):将这个数组每隔gap(gap逐渐减少,直到为1)个计为一组,进行插入排序,使小的数更快到前面,大的数更快到后面,使数组达到接近有序,最后进行一次(gap=1)插入排序。

代码:

void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap>1)
	{
		gap /= 2;
		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

注意点:end的下一个为end+gap,因此i<n-gap,防止越界,比较也是间隔gap比较。

4.选择排序

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

空间复杂度:O(1)。

稳定性:不稳定。

选择排序思路:每次从剩余数选一个最大或最小的数放到正确的位置,之后不再遍历这个位置,直到有序。

代码:

void SelectSort(int* a, int n)
{
	int l = 0;
	int r = n - 1;
	while (l < r)
	{
		int begin = l;
		int end = r;
		int mini = l;
		int maxi = l;
		for (int i = l + 1; i <= r; i++)
		{
			if (a[i] < a[mini])
			{
  			    mini = i;
			}
			if (a[i] > a[maxi])
			{
				maxi = i;
			}
		}
		Swap(&a[mini], &a[begin]);
		if (maxi == begin)
		{
			maxi = mini;
		}
		Swap(&a[maxi], &a[end]);
		l++;
		r--;
	}
}

为了减少遍历趟数,我们一次选出两个数,最大值与最小值放到开始与结尾的位置,下一次缩小范围,直到有序。

注意点:如果最大值在开始遍历的位置即begin,那么将最小值与begin位置的数交换时,最大值被换走,但maxi未更新,此时就无法使最大值达end位置,因此需要特殊判断一下,更新maxi的位置。

5.堆排序

时间复杂度:O(nlogn)。

空间复杂度:O(1)。

稳定性:不稳定。

堆排序思路:堆排序借助堆的特性,建堆,不断将堆顶的数与剩余数中最后一个数交换,再去掉最后一个数重新建堆,直至有序。

代码:

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void adjustdown(int* arr, int parent,int n)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child+1<n&& arr[child + 1]<arr[child] )
		{
			child += 1;
		}
		if (arr[parent] > arr[child])
		{
			Swap(&arr[parent], &arr[child]);
			parent = child;
			child= parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
void HeapSort(int* a, int n)
{
	//建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		adjustdown(a, i, n);
	}
	int end = n - 1;
	while (end)
	{
		Swap(&a[0], &a[end]);
		end--;
		adjustdown(a, 0, end);
	}
}

建堆需要利用向下调整算法,从最后一个叶节点的父节点开始,依次往前进行向下调整,最后建出大堆,将堆顶的数与最后一个数交换,再将剩余数(去掉最大值)重新建堆,直至有序。

注意点:子节点child的父节点为parent=(child-1)/2,最后一个叶节点的父节点为(n-1-1)/2。

6.快速排序

时间复杂度:O(nlogn)。

空间复杂度:O(logn)。

稳定性:不稳定。

快速排序思路:从数组中选取一个数key使其到达正确的位置,如果key左边有序,右边有序,那么数组就有序。

代码:

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//三数取中
int get_mid(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[left] < right)
	{
		if (a[left] >a[mid])
		{
			return left;
		}
		else if(a[right]<a[mid])
		{
			return right;
		}
		else
		{
			return mid;
		}
	}
	else
	{
		if (a[right] > a[mid])
		{
			return right;
		}
		else if (a[left] < a[mid])
		{
			return left;
		}
		else
		{
			return mid;
		}
	}
}
//使key到达正确位置
int PartSort1(int* a, int left, int right)
{
	int keyi = left;
	while (left < right)
	{
		while (left < right&&a[right] >= a[keyi])
		{
			right--;
		}
		while (left < right&&a[left] <= a[keyi])
		{
			left++;
		}
		Swap(&a[left], &a[right]);
	}
	Swap(&a[keyi], &a[left]);
	return left;
}
void QuickSort(int* a, int left, int right)
{
	if(left>=right)
    return ;
	int mid = get_mid(a, left, right);
	Swap(&a[left],& a[mid]);
	int keyi=PartSort1(a, left, right);
	QuickSort(a, left, keyi - 1);
	QuickSort(a, keyi+1, right);
}

如何使key到达正确位置呢?我们选取第一个数为key,用右指针找到比key小的,左指针找到比key大的,然后交换,最后左右指针相遇,将相遇点的数与key交换,key就到达正确的位置了,再递归key左右区间,使其有序。通过三数取中保证每次不是最坏情况(即key已是正确位置,如果数组已有序,没有三数取中快排的时间复杂度为(O(n^2)))。

注意点:

1.递归停止条件为left>=right,不是left==right,因为如果key在最右边时,key的右区间为[key+1,key],此时left>right。

2.在使key到达正确位置的过程中,left与right可能越界(即key已在正确位置),因此需要再内部循环加上left<rught。

7.归并排序

时间复杂度:O(nlogn)。

空间复杂度:O(n)。

稳定性:稳定。

归并排序思路:借用将两个有序数组合并成一个有序数组的思路,将一个数组分为左右两部分,如果左边有序,右边也有序,即可借助另一个数组将这两部分合并为完整的有序数组。

代码:

void partsort(int* str, int* a, int left,int right)
{
	if (left == right)
	{
		return;
	}
	int mid = (left + right) / 2;
	partsort(str, a, left, mid);
	partsort(str, a, mid+1, right);
	int begin1 = left, end1 = mid;
	int begin2 = mid+1, end2 = right;
	int c = left;
	while (begin1<=end1&&begin2<=end2)
	{
		if(a[begin1] <= a[begin2])
		{
			str[c++] = a[begin1++];
		}
		else
		{
			str[c++] = a[begin2++];
		}
	}
	while (begin1<=end1)
	{
		str[c++] = a[begin1++];
	}
	while (begin2<=end2)
	{
		str[c++] = a[begin2++];
	}
	memcpy(a+left, str+left, sizeof(int) * (right - left + 1));
}
void MergeSort(int* a, int n)
{
	int* str = (int*)malloc(sizeof(int) * n);
	partsort(str, a, 0,n-1);
}

先递归左右区间使其有序,然后开始对比,将数插进临时数组,最后在拷贝回去。

注意点:在某个区间进行排序时,无论时插数进临时数组还是拷贝回去,记得要加上区间起始值left,保证区间的数仍在该区间内。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值