排序--面经

视频学习网址:点击打开链接

参考书籍《算法导论》

Pre比较:

理论时间:

                          堆排序      归并排序        快速排序
最坏时间        O(nlogn)     O(nlogn)        O(n^2)
最好时间        O(nlogn)     O(nlogn)        O(nlogn)
平均时间        O(nlogn)     O(nlogn)        O(nlogn)

空间复杂度     O(1)             O(n)              ???这里不对啊,我觉得是O(1)啊(ps:这里的空间复杂度还包括了递归的过程,由于快排是递归实现的,所以有log(n)到n的空间复杂度,堆排序如果用递归实现也是logn的空间复杂度,不过堆排序可以改成非递归实现)


实际测量:

4种非平方级的排序:
希尔排序,堆排序,归并排序,快速排序

我测试的平均排序时间:数据是随机整数,时间单位是秒
数据规模    快速排序    归并排序    希尔排序    堆排序
1000万       0.75           1.22          1.77          3.57
5000万       3.78           6.29          9.48         26.54  
1亿             7.65          13.06        18.79        61.31

堆排序是最差的。
这是算法硬伤,没办法的。因为每次取一个最大值和堆底部的数据(记为X)交换,重新筛选堆,把堆顶的X调整到位,有很大可能是依旧调整到堆的底部(堆的底部X显然是比较小的数,才会在底部),然后再次和堆顶最大值交换,再调整下来。
从上面看出,堆排序做了许多无用功。


排序算法稳定性定义:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。

堆排序、快速排序、希尔排序、直接选择排序   不是稳定的排序算法,(快排和直接选择排序都是在发生交换的时候破坏的,希尔排序由于步长大于1时开头就会跳过一些元素,堆排序在维护堆的时候会破坏稳定性)

而基数排序、冒泡排序、直接插入排序、折半插入排序、归并排序、计数排序   是稳定的排序算法。

参考:百度百科

参考视频:点击打开链接

1 冒泡排序:两两比较,大的放后;关注for循环中的两个变量的变化范围;时间复杂度O(n^2)

#include <iostream>

class BubbleSort
{
public:
	BubbleSort(){};//默认构造函数
	BubbleSort(BubbleSort &other){};//拷贝构造函数

	//冒泡排序函数
	int *bubbleSort(int *pa, int n);
private:

};


//冒泡排序函数
int *BubbleSort::bubbleSort(int *pa, int n)
{
	int temp = 0;
	//for循环,两两比较排序
	for (int j = 0; j < n - 1; j++)
	{
		for (int k = 0; k < n - 1 - j; k++)
		{
			if (pa[k]>pa[k + 1])
			{
				temp = pa[k];
				pa[k] = pa[k + 1];
				pa[k + 1] = temp;
			}
		}
	}

	//返回
	return pa;
}

2 插入排序:逐个选取数组中元素,与前面元素比较插入;时间复杂度O(n^2)

class InsertionSort {
public:
	int* insertionSort(int* A, int n) 
	{
		// write code here
		int key = 0;
		//遍历数组中每一个元素
		for (int i = 1; i < n; i++)
		{
			key = A[i];
			int j = i - 1;
			//与前面的元素逐个比较,大于则插入
			while (j>=0 && A[j]>key)
			{
				A[j + 1] = A[j];
				j--;
			}
			A[j + 1] = key;
		}

		return A;
	};

private:

};

3 选择排序:选择最大值,放到数组的后面;时间复杂度O(n^2)

class SelectionSort {
public:
	int* selectionSort(int* A, int n) {
		// write code here
		int maxKey = 0;
		int maxInd = 0;
		int temp = 0;
		//每次选择最大的,放在数组的后面
		for (int i = n - 1; i >= 0; i--)
		{	
			maxKey = A[0];
			maxInd = 0;

			//找出最大的
			for (int j = 0; j <= i; j++)
			{
				if (A[j] > maxKey)
				{
					maxKey = A[j];
					maxInd = j;
				}
			}

			//交换 最大值 和 查找范围末尾位置的值
			temp = A[i];
			A[i] = A[maxInd];
			A[maxInd] = temp;
		}
		return A;
	}
	
};

4 归并排序:递归分解为子问题,将子问题合并。时间复杂度O(nlogn),空间上需要动态分配内存辅助,额外的空间需求

class MergeSort
{
public:
	int *mergeSort(int *A,int n)
	{
		mergeSortAC(A, 0, n - 1);
		return A;
	}
	
private:
	//将pa指向的数组,划分成两个小数组,排序后再合并
	int mergeSortAC(int *pa, int a, int c);

	//合并函数
	int merge(int *pa, int a, int b, int c);
};


//将pa指向的数组,划分成两个小数组,排序后再合并
int MergeSort::mergeSortAC(int *pa, int a, int c)
{
	//边界条件:只有一个元素,返回
	if (a >= c) return 0;

	int b = (a + c) >> 1;

	//对[a,b],[b+1,c]的两个数组分别排序,递归调用
	mergeSortAC(pa, a, b);
	mergeSortAC(pa, b + 1, c);

	//合并两个已经排好序的数组
	merge(pa, a, b, c);

	return 0;
}
//合并函数
int MergeSort::merge(int *pa, int a, int b, int c)
{
	//分配两个动态数组
	int *pb = new int[b - a + 2];
	assert(pb != NULL);
	int *pc = new int[c - b + 1];
	assert(pc != NULL);

	//将要排序的部分复制到动态数组中
	int i = a;
	int j = b + 1;

	while (i <= b)
	{
		pb[i - a] = pa[i];
		i++;
	}
	while (j <= c)
	{
		pc[j - b - 1] = pa[j];
		j++;
	}

	//为动态数组添加末尾无穷大值
	pb[b - a + 1] = INT_MAX;
	pc[c - b] = INT_MAX;

	//逐个比较复制到pa中
	i = j = 0;
	for (int k = a; k <= c; k++)
	{
		if (pb[i] < pc[j])
		{
			pa[k] = pb[i];
			i++;
		}
		else
		{
			pa[k] = pc[j];
			j++;
		}
	}

	//释放动态内存
	delete[] pb;
	delete[] pc;

	//返回
	return 0;

}

5 快速排序:随机抽取一个数,小的放左边,大的放右边。利用小于等于区间,可以原址排序。使用随机函数,引入随机选择。理论最差情况,时间复杂度O(n^2),期望复杂度是O(nlogn),实际使用中,快排的速度是最快的,而且由于是原址排序,所以空间需求少,不像归并排序需要O(n)的辅助空间。

#include <stdlib.h>
#include <time.h>

class QuickSort
{
public:
	int *quickSort(int *A, int n)
	{
		quickSortBC(A,0,n-1);
		return A;
	}
	int *quickSortBC(int *A, int b, int c)
	{
		//边界条件
		if (b >= c) return 0;

		int i = 0;
		i = partition(A, b, c);//划分

		quickSortBC(A, b, i-1);//对划分的子集,快速排序
		quickSortBC(A, i+1, c);

		return 0;
	}
	//划分函数
	int partition(int *A, int b, int c)
	{
		//边界条件
		if (b >= c) return 0;

		//为快排加入随机选择
		srand((unsigned)time(0));
		int ind = b + rand() % (c - b + 1);//ind取值范围[b,c]
		int key = A[ind];
		//将ind对应的元素,换到数组末尾
		A[ind] = A[c];
		A[c] = key;

		int temp = key;
		int i = b - 1;
		int j = b - 1;
		while (j < c)
		{
			j++;
			if (A[j] <= key)
			{
				i++;
				temp = A[i];
				A[i] = A[j];
				A[j] = temp;
			}
		}

		//返回key值的索引
		return i;
	}

private:

};


6 堆排序:利用最大堆,每次取堆根,然后维护堆;时间复杂度上界是O(nlogn),堆排在实际中并不快,做了很多无用的比较;

class HeapSort
{
public:
	int *heapSort(int *A, int n);
private:
	int buildHeap(int *A, int size);//建堆
	int maxHeap(int *A, int ind, int size);//维护最大堆
	inline int exchange(int *A, int a, int b);//交换两个元素
};


int *HeapSort::heapSort(int *A, int n)
{
	//将最小元素换到A[0],因为A[0]是不参与建堆的
	int minEle = A[0];
	int ind = 0;
	for (int i = 0; i < n; i++)
	{
		if (minEle>A[i])
		{
			minEle = A[i];
			ind = i;
		}
	}
	A[ind] = A[0];
	A[0] = minEle;

	//建堆
	buildHeap(A, n - 1);

	//循环取堆根放到数组A末尾,维护堆
	for (int size = n - 1; size > 1;)
	{
		exchange(A, 1, size);
		size--;
		maxHeap(A, 1, size);
	}

	return A;
}
int HeapSort::buildHeap(int *A, int size)//建堆
{
	//from size/2 to 1
	for (int i = size >> 1; i > 0; i--)
	{
		maxHeap(A, i, size);
	}

	return 0;
}
int HeapSort::maxHeap(int *A, int ind, int size)//维护最大堆
{
	int largest = ind;
	int left = (ind << 1);
	int right = (ind << 1) + 1;

	if (left <= size && A[left] > A[ind])//与左孩子比较
	{
		largest = left;
	}
	if (right <= size && A[right] > A[largest])//与右孩子比较
	{
		largest = right;
	}
	if (largest != ind)
	{
		exchange(A, largest, ind);
		maxHeap(A, largest, size);
	}
	return 0;
}
inline int HeapSort::exchange(int *A, int a, int b)
{
	int temp = A[a];
	A[a] = A[b];
	A[b] = temp;
	return 0;
}


7 希尔排序




















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值