数据结构 - 排序总结

偶然发现菜鸟教程有着,整挺好存一下:https://www.runoob.com/w3cnote/radix-sort.html
快速排序:分治思想的冒泡排序
sf:https://segmentfault.com/a/1190000017314698 一篇文章让你真正了解快速排序阮一峰


没有编号的反人类目录,不过可以看右方CSDN默认目录。

数据结构复习

排序

名次排序/计数排序/原地重排

名次计算

名次是所有比它小的元素个数加上在它左边出现的与它相同的元素个数。

对于每个i的值,比较次数为i,因此总的比较次数为1+2+3+...+n-1=(n-1)n/2.

template<class T>
void rank(T a[], int n, int r[]) {
	for(int i = 1; i < n; ++i) 
		r[i] = 0;
	for(int i = 1; i < n; ++i) 
		for(int j = 0; j < i; ++j)
			if(a[j] <= a[i]) 
				r[i] ++;
			else r[j] ++;
}

名次排序

利用附加数组的计数排序

2*n次移动+之前的(n-1)n/2次比较。

template<class T> 
void rearrange(T a[], int n, int r[]) {
	T *u = new T[n];
	for(int i = 0; i < n; ++i)
		u[r[i]] = a[i];
	for(int i = 0; i < n; ++i)
		a[i] = u[i];
	delete[] u;
}
原地重排

交换次数从最少0(初始即有序)到最多2(n-1).每次交换都可以将一个a[i]移动到正确位置。

比前者空间减少,时间复杂度增加一丢丢。

temlate<class T>
void rearrange(T a[], int n, int r[]) {
	for(int i = 0; i < n; ++i)
		while(r[i] != i) {
			int t = r[i];
			swap(a[i], a[t]);
			swap(r[i], r[t]);
		}
}

选择排序

依次找剩下的元素中的最大值。

总的比较次数(n-1)n/2,元素移动次数3(n-1).

template<class T>
void selectionSort(T a[], int n) {
	for(int size = n; size > 1; size--) {
		int j = indexOfMax(a, size);
		swap(a[j], a[size-1]);
	}
}

及时终止的选择排序

检查如果已经有序,那么就没有必要继续了。

template<class T>
void selectionSort(T a[], int n) {
	bool sorted = false;
	for(int size = n; !sorted && (size > 1); size--) {
		int indexOfMax = 0;
		sorted = true;
		for(int i = 1; i < size; i++)
			if(a[indexOfMax] <= a[i])
				indexOfMax = i;
			else sorted = false;
			swap(a[indexOfMax], a[size-1]);
	}
}

及时终止的冒泡排序

时间复杂度也为(n-1)n/2,最好情况n-1

template<class T>;
bool bubble(T a[], int n) {
	bool swapped = false;
	for(int i = 0; i < n-1; i++)
		if(a[i] > a[i+1]) {
			swapped = true;
			swap(a[i], a[i+1]);
		}
	return swapped;
}

template<class T>;
void bubbleSort(T a[], int n) {
	for(int i = n; (i > 1) && bubble(a,i); i--);
}

插入排序

最好的比较次数是n-1,最坏的比较次数是(n-1)n/2.

template<class T>
void insert(T a[], int n, const T& x) {
	int i;
	for(i = n-1; i >= 0 && x < a[i]; i--)
		a[i+1] = a[i];
	a[i+1] = x;
}

template<class T>
void insertion(T a[], int n) {
	for(int i = 1; i < n; ++i) {
		T t = a[i];
		insert(a, i, t);
	}
}

版本2,其实没啥不同

template<class T>
void insertion(T a[], int n) {
	for(int i = 1; i < n; ++i) {
		T t = a[i];
		int j;
		for(j = i-1; j >= 0 && t < a[j]; --j)
			a[j+1] = a[j];
		a[j+1] = t;
	}
}

堆 & 堆排序

通过初始化也可以排序,初始化好快啊,O(n)!(也就找个最大元素,其实理应这么快)

void init() {
	for(int root = heapSize/2; root >= 1; --root) {
		int rootValue = heap[root];
		int child = 2*root;
		while(child <= heapSize) {
			if(child < heapSize && heap[child] < heap[child+1])
				child ++;
			if(rootValue >= heap[child])
				break;
			heap[child/2] = heap[child];
			child *= 2;
		}
		heap[child/2] = rootValue;
	}
}

通过插入删除,都是O(logn)

void insert(int x) {
	int nw = ++heapSize;
	while(nw != 1 && heap[nw/2] < x) {//父节点更小 需要下移
		heap[nw] = heap[nw/2]; //下移
		nw /= 2;
	}
	heap[nw] = x;
}

void pop() {
	int last = heap[heapSize--]; //取出最后一个元素
	int nw = 1, child = 2;
	while (child <= heapSize) {
		if(child < heapSize && heap[child] < heap[child+1])
			child ++;
		if(last >= heap[child]) // 比孩子们都大
			break;
		heap[nw] = heap[child]; //孩子挪上来
		nw = child;
		child *= 2;
	}
	heap[nw] = last;
}

归并排序 分治

书上代码看不懂。

C语言版贼好用而且短好喜欢这个,思想是一样的,考试考思路没问题。

树形 复杂度O(nlogn).

比较好的教程:常用12大排序算法之八:(递归和非递归)归并排序

void Merge(int sourceArr[],int tempArr[], int startIndex, int midIndex, int endIndex) {
    int i = startIndex, j=midIndex+1, k = startIndex;
    while(i!=midIndex+1 && j!=endIndex+1) {
        if(sourceArr[i] > sourceArr[j])
            tempArr[k++] = sourceArr[j++];
        else
            tempArr[k++] = sourceArr[i++];
    }
    while(i != midIndex+1)
        tempArr[k++] = sourceArr[i++];
    while(j != endIndex+1)
        tempArr[k++] = sourceArr[j++];
    for(i=startIndex; i<=endIndex; i++)
        sourceArr[i] = tempArr[i];
}
void MergeSort(int sourceArr[], int tempArr[], int startIndex, int endIndex) {
    int midIndex;
    if(startIndex < endIndex) {
        midIndex = (startIndex + endIndex) / 2;
        MergeSort(sourceArr, tempArr, startIndex, midIndex);
        MergeSort(sourceArr, tempArr, midIndex+1, endIndex);
        Merge(sourceArr, tempArr, startIndex, midIndex, endIndex);
    }
}

快速排序 分治

思想,选取一个支点n,比n小的元素放在n的左边作为左段,比n大的放在n的右边作为右段,然后分别在左段和右段中进行快排,最后元素序列是左段+n+右段便是有序0。

见过的最通俗易懂的代码。时间复杂度O(nlogn),最坏复杂度O(n^2).

void qsort(int a[], int left_index, int right_index)
{
    int left, right, pivot;
    if(left_index >= right_index) 
        return;
    left = left_index;
    right = right_index;
    pivot = a[(left_index + right_index) /2];
    while(left <= right) {
        while(a[left] < pivot) left++;
        while(a[right] > pivot) right--;
        if(left <= right) {
            swap(a[left],a[right]);
            left++; right--;
        }   
    }
    qsort(a,left_index,right);
    qsort(a,left,right_index);
}

箱子排序

即桶排。复杂度O(n).

基数排序

时间复杂度O(n),但是有个大常数c,所以可能不如快排快

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E6bwuoF4-1593509465227)(C:\Users\wbxl2\AppData\Roaming\Typora\typora-user-images\image-20200104221341550.png)]

把数的每一位分离出来,从个位到高位依次桶排序一遍。

时间复杂度O(n),有个大常数。

排序总结

图解十大排序算法

稳定排序

排序算法的稳定性

一个排序方法能够保持同值元素之间的相对次序,那么该方法称为稳定排序。

堆排序快速排序希尔排序直接选择排序是不稳定的排序算法

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

稳定:插入排序、冒泡排序、归并排序、计数排序、基数排序、桶排序

不稳定:快速排序、堆排序、希尔排序、选择排序

冒泡排序 平均性能 最坏情况下性能 最好情况下性能 稳定性
冒泡排序 n^2 n^2 n 稳定
计数排序 n^2 n^2 n^2 稳定
插入排序 n^2 n^2 n 稳定
选择排序 n^2 n^2 n^2 不稳定
堆排序 nlogn nlogn nlogn 不稳定
归并排序 nlogn nlogn nlogn 稳定
快速排序 n^2 nlogn nlogn 不稳定

copy函数的三个参数

copy(a, a+n, tmp)表示从atmp.

渐进记法

没太看懂。估计不考。

image-20200104191152884

散列表

线性探查

如果哈希的地方已经有元素,那么找下一个可用桶。

要会画图模拟。

比较难的删除操作代码,一次检查每个桶。

template<class T>
int hashTable<T>::erase(const T& theElement) {
	int root = search(theElement);
	delete table[root];
	table[root] = NULL;
	
	int cnt = 0;
	int i = (root+1)%divisor;
	int pre = (i-1+divisor)%divisor;
	while(table[i] != NULL && i != root) {
		int thisfather = (*table[i])%divisor;
		if((thisfather!= i)&&(((thisfather<=pre)&&(i>pre))||((thisfather>i)&&((pre<i)||(pre>=thisfather))))) {
			table[pre] = table[i];
			table[i] = NULL;
			pre = i;
			cnt++;
		}
		i = (i+1)%divisor;
	}
	
	return cnt;
}

链式散列

比较简单。要会画图。

负载因子

α=n/(b=D)\alpha=n/(b=D)

对于线性探查:

一次成功搜索Un12(1+1(1α)2)U_n \approx \frac{1}{2}(1+\frac{1}{(1-\alpha)^2})

不成功搜索Sn12(1+1(1α))S_n \approx \frac{1}{2}(1+\frac{1}{(1-\alpha)})

一般情况下小于0.75.

性能

线性探查空间少,平均性能链式散列快。

树的还原

前序遍历:根左右

中序遍历:左根右

后序遍历:左右根

根据前中序列,求后序

根据前序遍历划分中序遍历

中后求前也类似,要注意的是中后的时候会倒序看根节点会先右再左。

huffman树

建立过程,每次取出两棵权重最小的二叉树。最后获得编码。

WEP是所有的路径长*频率再求和,也就是最终编码的位数。

AVL树

可视化 https://www.cs.usfca.edu/~galles/visualization/AVLtree.html

LL LR RR RL

B-树

课本上的插入删除很棒。

拓扑排序

有向无环图(DAG)才有拓扑排序。

  1. 从 DAG 图中选择一个 没有前驱(即入度为0)的顶点并输出。

  2. 从图中删除该顶点和所有以它为起点的有向边。

  3. 重复 1 和 2 直到当前的 DAG 图为空或当前图中不存在无前驱的顶点为止。后一种情况说明有向图中必然存在环。

Dijkstra

过程,要所有已知的最短边去更新邻接点。

正确性:当前节点能够被取出来说明已经没有其他边可以更新此边了。

Prim

记录哪些点已经在生成树中

当前生成树的所有邻接边中选最小的

矩阵

特殊矩阵的压缩

对角矩阵

三对角矩阵(分类讨论就可)

三角矩阵(n(n+1)/2),下三角矩阵 map(i,j)=i(i1)/2+j1,ijmap(i,j)=i(i-1)/2+j-1, i \geq j

稀疏矩阵

线性储存

相加

行主索引判断一下前后次序,扫描一遍,小的加进去,相等就相加,直到其中一个到了末尾,最后剩下的元素推进去。

相乘

i,k中 寻找 k,j,进行累加。 相当于ans[i][j]要枚举中间元素k

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章