七种排序算法及其复杂度

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ZX714311728/article/details/62440791

1.冒泡排序

时间复杂度:平均:O(N^2),最坏:O(N^2),最好:O(N)

思想:从后往前开始,相邻的两两进行比较,将小的逐渐移到前面。(也可以从前往后开始两两相邻的进行比较)

代码:

void BubbleSort(int *a, int N)
{
	if (a == NULL || N <= 0)
		return ;

	int i, j;
	for (i = 0; i < N; i++)
	{
		for (j = N-1; j > i; j--)
		{
			if (a[j - 1] > a[j])
				swap(&a[j - 1], &a[j]);
		}
	}
}
冒泡排序的优化:若一趟下来没有数据交换,则说明此时数组已经有序,此时算法可以结束。而原来的冒泡排序还是一遍遍比较,直到循环结束。

代码:

void BubbleSort2(int *a, int N)
{
	if (a == NULL || N <= 0)
		return ;

	int i, j;
	bool flag = true;
	for (i = 0; i < N && flag; i++)
	{
		flag = false;
		for (j = N-1; j > i; j--)
		{
			if (a[j - 1] > a[j])
			{
				swap(&a[j - 1], &a[j]);
				flag = true;
			}
		}
	}
}


2.选择排序

时间复杂度:平均:O(N^2),最坏:O(N^2),最好:O(N^2)

思想:每次从右边中的数选一个最小的和左边当前位置的进行较换

分析:最大特点就是交换移动数据的次数相当少,这样也就节约了相应的时间。性能上略优于冒泡排序。

代码:

void SelectSort(int *a, int N)
{
	if (a == NULL || N <= 0)
		return ;
	
	for (int i = 0; i < N; i++)
	{
		int min = i;
		for (int j = i; j < N; j++)
		{
			if (a[j] < a[min])
				min = j;
		}
		swap(&a[i], &a[min]);
	}
}

3.插入排序

时间复杂度:平均:O(N^2),最坏:O(N^2),最好:O(N)

思想:默认认为位置0-(i-1)是排好序的。将位置i上元素赋给临时变量,与左边一个值判断,若大于左边的,则位置不动。若小于左边的,则将左边的值移到当前位置,临时变量再继续同下一个左边的元素进行比较。

分析:直接插入排序性能要比冒泡排序和选择排序好一些。

代码:

void InsertSort(int *a, int N)
{
	if (a == NULL || N <= 0)
		return ;

	int i, j;
	for (i = 1; i < N; i++)
	{
		int tmp = a[i];
		for (j = i; j > 0; j--)
		{
			if (tmp < a[j - 1])
				a[j] = a[j - 1];
			else
				break;
		}
		a[j] = tmp;
	}
}


4.希尔排序

时间复杂度:平均:O(N^3/2),最坏:O(N^2),最好:O(N)

思想:希尔排序又叫减小增量排序。本质是分组的插入排序。先将元素分成几个分组,每个分组进行插入排序。然后不断减小分组的间隔,直至间隔为1,就变成了插入排序。

分析:增量序列的最后一个增量值必须为1才行。希尔排序并不稳定。
代码:

void ShellSort(int *a, int N)
{
	if (a == NULL || N <= 0)
		return ;

	int gap;
	int i, j;
	for (gap = N/2; gap > 0; gap /= 2)
		for (i = gap; i < N; i++) //实质是插入排序,将a[i]和左边比较,插入到正确位置
		{
			int tmp = a[i];
			for (j = i; j >= gap; j -= gap)  //和插入排序的操作一样
			{
				if (a[j - gap] > tmp)
					a[j] = a[j - gap];
				else
					break; //默认为左边是已经排序的,若比左边的大,说明此时是正确的位置。
			}
			a[j] = tmp;
		}
}


5.堆排序

时间复杂度:平均:O(N*logN),最坏:O(N*logN),最好:O(N*logN)

思想:先将输入数组进行堆化操作,构成一个最小堆,每次将堆顶元素与末尾元素互换,再将剩下的元素恢复堆序。

若使用最小堆,则排序后是递减;若使用最大堆,则排序后是递增。

分析:1.结点i的左右子节点下标分别为2i+1,2i+2;2.结点i的父结点下标为(i-1)/2;3.一颗完全二叉树的最后一个拥有子结点的结点下标为(n/2 - 1),n为结点数

代码:

//下滤操作
void MinHeapFixDown(int *a, int i, int n)
{
	if (a == NULL || i < 0 || n < 0 || i >= n)
		return ;
	
	int tmp = a[i];
	int j = 2 * i + 1;    //左子结点
	while (j < n)
	{
		if (j + 1 < n && a[j + 1] < a[j])    //取左右子结点中小的那个
			j++;
		if (tmp <= a[j])  //父结点比子结点小
			break;
		
		a[i] = a[j];      //将子结点上滤,移到父结点位置
		i = j;            //将父结点移到子结点位置
		j = 2 * i + 1;    //取子结点的子结点
	}
	a[i] = tmp;
}

//堆排序
void HeapSort(int *a, int n)     //用最小堆排序后是递减,用最大堆排序后是递增。
{
	if (a == NULL || n <= 0)
		return ;

	int i;
	for (i = n / 2 - 1; i >= 0; i--)        //堆化操作
		MinHeapFixDown(a, i, n);
	for (i = n - 1; i > 0; i--)
	{
		swap(&a[0], &a[i]);             //交换堆顶元素和堆末尾元素
		MinHeapFixDown(a, 0, i);        //恢复剩余元素的堆序性质
	}
}


6.归并排序

时间复杂度:平均:O(N*logN),最坏:O(N*logN),最好:O(N*logN)

思想:若有两个已排序的数组,合并时谁小就取谁。合并排序就是将左、右边排序后再合并,对于左边部分可递归的再分成左右两部分,每部分拍好序后合并形成左边排序的部分。右边也一样。

void Merge(int *a, int first, int mid, int last, int *tmp)
{
	int lpos = first;
	int rpos = mid + 1;
	int pos = 0;

	while (lpos <= mid && rpos <= last)
	{
		if (a[lpos] < a[rpos])
			tmp[pos++] = a[lpos++];
		else
			tmp[pos++] = a[rpos++];
	}
	while (lpos <= mid)
		tmp[pos++] = a[lpos++];
	while (rpos <= last)
		tmp[pos++] = a[rpos++];

	for (int i = 0; i < pos; i++)
		a[first + i] = tmp[i];
}

void MSort(int *a, int first, int last, int *tmp)
{
	if (first < last)
	{
		int mid = (first + last) / 2;
		MSort(a, first, mid, tmp);        //左边有序
		MSort(a, mid + 1, last, tmp);     //右边有序
		Merge(a, first, mid, last, tmp);  //合并左右两个有序数列
	}
}

void MergeSort(int *a, int n)
{
	if (a == NULL || n <= 0)
		return ;
	int *tmp = new int[n];
	if (tmp == NULL)
		return ;
	MSort(a, 0, n - 1, tmp);
	delete [] tmp;
}

7.快速排序

时间复杂度:平均:O(N*logN),最坏:O(N^2),最好:O(N*logN)

思想:每次从数组中选出一个基准数,再将剩余元素中小于基准数的放在其左边,大于基准数的放在其右边。再继续对左右两部分继续进行相同操作。

分析:下面代码对一般的快速排序进行了优化。

优化部分有:

1.当剩余元素小于5(<=20)个时,进行插入排序,而不是全部都进行快速排序。优点:减小了递归深度,提升了效率。因为对于小数组的排序,快速排序不如插入排序快。

2.采用了三数中值法,而不是取数组中第1个元素。优点:当数组是准排序或逆序时,传统方法会将剩余元素全部移到一边,而三数中值法可避免这种情况,而且还可以提升排序效率。

3.在三数中值法的函数内,对左,中,右三个位置上元素进行排序。优点:本来它们就应该在该位置,提前将它们拍序了,还可以起到警戒标记,不用担心j越界。

4.将基准数放在right-1的位置,不用担心i越界。等一遍排序结束后,再将基准数和i位置元素进行交换。

int Median3(int *a, int left, int right)
{
	int center = (left + right) / 2;
	if (a[left] > a[center])
		swap(&a[left], &a[center]);
	if (a[left] > a[right])
		swap(&a[left], &a[right]);
	if (a[center] > a[right])
		swap(&a[center], &a[right]);
	swap(&a[center], &a[right - 1]);
	return a[right - 1];
}

void QSort(int *a, int left, int right)
{
	if (right - left >= 5)
	{
		int piovt = Median3(a, left, right);
		int i = left;
		int j = right - 1;
		for (;;)
		{
			while (a[++i] < piovt) {}
			while (a[--j] > piovt) {}
			if (i < j)
				swap(&a[i], &a[j]);
			else
				break;
		}
		swap(&a[i], &a[right - 1]);
		QSort(a, left, i - 1);
		QSort(a, i + 1, right);
	}
	else
		InsertSort(a + left, right - left + 1);
}

void QuickSort(int *a, int n)
{
	if (a == NULL || n <= 0)
		return ;
	QSort(a, 0, n - 1);
}




猜你喜欢

转载自blog.csdn.net/ZX714311728/article/details/62440791