Algorithm in Practice

Algorithm in Practice

Author: Zhong-Liang Xiang
Date: Aug. 1st, 2017

不完整, 部分排序和查询算法, 需添加.

Prerequisite

生成随机整数数组,打印数组, 元素交换.

#include <stdlib.h>
#include <iostream>
#include <time.h>
using namespace std;

#define MAX 10 // 数组最大长度

void initList(int *, int len, int m);
void printList(int *, int len);
void swap(int *, int *);

int main() {
	int L[MAX];
	initList(L, MAX, 100);
	printList(L, MAX);

	return 0;
}

// 初始化 a list with len 个(0, m)之间的随机数
void initList(int *L, int len, int m) {
	srand((unsigned) time(NULL)); //初始化随机数种子
	for (int i = 0; i < len; i++) //产生len个随机数
		*(L + i) = (rand() % m);    //0-m之间随机数
}

//打印1维数组;
void printList(int *L, int len) {
	for (int i = 0; i < len; i++)
		cout << *(L + i) << ' ';
	cout << endl;
}

void swap(int *l, int *r) {
	int temp = *l; *l = *r; *r = temp;
}

0. Searching

// 无序数组顺序查找
int search_Seq(int *L, int len, int key) {
	int result = -1;
	for (int i = 0; i < len; i++) {
		if (*(L + i) == key) {
			result = i;
			break;
		}
	}
	if (result < 0)
		result = -1;
	return result;
}

针对下面数组,查找7:
0 1 2 3 4 5 6 7 8 9
lo                             hi

Binary Search 算法描述:

while(lo <= hi):
		mid = (lo + hi)/2 = 4
		若 key == mid, 输出mid;
		否则 若key < mid, 调整 hi = mid - 1;
		否则 若key > mid, 调整 lo = mid + 1;
返回 -1;
// 有序数组二分查找
int bin_Seq(int *L, int len, int key) {
	int lo = 0, hi = len - 1, mid;

	while (lo <= hi) {
		mid = (lo + hi) / 2;
		if (key == L[mid])
			return mid;
		if (key < L[mid])
			hi = mid - 1;
		else if (key > L[mid])
			lo = mid + 1;
	}
	// 循环退出,说明没找到key,返回-1
	return -1;
}

1. Sorting

默认升序.

  • 插入排序
    • 直接插入排序
    • 二分插入排序
    • Shell-Sort
  • 交换排序
    • Bubble-Sort
    • Quick-Sort
  • 选择排序
    • 直选排序
    • 树型排序
    • 堆排序
  • Merge-Sort
    • 二路归并排序
    • 多路归并排序
  • 分配排序
    • 关键字排序
    • 基数排序

1.1 Insertion-Sort

1.1.1 直接插入排序

  • Stable
  • Time complexity (O(n^2))
  • Space complexity (O(1))

Idea: 左手 sorted 牌,右手拿新牌往左手牌里插.

// 代码片段
#define MAX 20 // 数组最大长度

void insertion_sort(int *L, int len){
	int key, i;
	for (int j = 1; j < len; j++){
		key = L[j];
		i = j - 1;
		// while循环目的: 移动 sorted 元素,为 key 找位置
		while(i >= 0 && L[i] > key){
			L[i + 1] = L[i];
			i -= 1;
		}
		// 循环退出,说明已为key找到插入位置,为 L[i+1] 处
		L[i + 1] = key;
	}
}

//测试
int main(){
	//数据准备
	int L[MAX];

	//排序前
	initList(L, MAX, 100); //0-100 random int number
	printList(L, MAX);

	//排序后s
	insertion_sort(L, MAX);
	printList(L, MAX);
}

1.1.2 二分插入排序

1.1.3 Shell 排序

1.2 交换排序

1.2.1 Bubble-Sort

  • Stable
  • Time complexity (O(n^2))
  • Space complexity (O(1))

Idea: 小泡泡往上升.
算法描述:

do while 终止条件: 本趟若无交换,表示数组已排好序,无需再排序
i < len - 1; 最后一个元素不扫
L[0]为leftElem, L[0+1]为rightElem
大元素升到索引高的位置
void bubble_sort(int *L, int len) {
	bool swapped;
	do {
		swapped = false;
		for (int i = 0; i < len - 1; i++) {
			if (*(L + i) > *(L + i + 1)) {
				swap(L + i, L + i + 1);
				// 本趟若无交换,表示数组已排好序,无需再排序
				swapped = true;
			}
		}
	} while (swapped);
}

1.2.2 Quick-Sort

  • Divide and conquer method
  • Unstable
  • Average time complexity (O(nlogn)), worst case time complexity (O(n^2))
  • Space complexity (O(1))

Idea: 应用分治法,选择某元素,如 L[lo],放入k中,让小(乱序)的排在该元素左侧,大的在右侧(乱序),k在中间,k所在的位置就是正确的位置.再对左右分别应用该方法.
Quick-Sort 算法描述:

1. 未排序数组找一元素作为标靶放入k中;
2. lo, hi 分别指向未排序数组首尾;
3. 从右向左扫描,若L[hi]>=key, hi--, 否则 L[lo] = L[hi];
4. 从左向右扫描,若L[lo]<=key, hi++, 否则 L[hi] = L[lo];

Divide and conquer algorithm 简书
三步骤: Divide Conquer Combine
分治法与 Recursive method 被认为是孪生兄弟.
针对下面数组, 设定未排序数组最左侧的元素为标靶,如 L[0], 其值为:3

 7 3 0 4 2 1 9 6 8
lo                             hi


1 2 3 0 4  7 6 8 9
lo              标靶           hi

int divide(int *L, int lo, int hi) {
	int k = L[lo];
	do {
		// 正向思考,写全条件
		while (lo < hi && L[hi] >= k) hi--;
		// 若能执行 if 里的 code, 需满足 L[hi] < k, 值得学习
		if (lo < hi) { L[lo] = L[hi]; lo++; }
		while (lo < hi && L[lo] <= k) lo++;
		if (lo < hi) {L[hi] = L[lo]; hi--; }
	} while (lo != hi); //哨兵
	L[lo] = k;
	return lo;
}

void quick_sort(int *L, int lo, int hi) {
	int mid;

	if (lo >= hi) return; // 只有1元素或lo>hi, 递归终止条件
	// 分
	mid = divide(L, lo, hi);

	// 治
	quick_sort(L, lo, mid - 1); //递归
	quick_sort(L, mid + 1, hi);
}

1.3 选择排序

1.3.3 Heap-Sort

  • Unstable
  • Average time complexity (O(nlogn)), worst case time complexity (O(nlogn)), 而 Quick-Sort 是(O(n^2)), 记录较少时不宜使用, 记录较多时表现很好.
  • Space complexity (O(1))

Idea: 完全二叉树,堆,性质为父节点大于等于子节点,根节点永远是最大(大顶堆)或最小节点(小顶堆),依次弹出根节点,自然就排序了.

1.3.3.1 Heap

重点是堆概念,大顶堆,小顶堆,以及针对这两种堆的建堆和出堆操作.熟悉后,堆排序自然就会了. 下面代码表现了大顶堆的操作,包括:

  • int Insert(MyHeap *pHeap, int nData) 插入新节点
    • int IncreaseKey(MyHeap *pHeap, int nPos) 比较调整新插入节点的位置,向上渗透
  • int PopMaxHeap(MyHeap *pHeap) 返回堆中根节点的值,并删除根节点,删除后,还需调整堆
// `ctrl+alt+f`: Format current file
#include <stdlib.h>
#include <iostream>
using namespace std;
// 大顶堆
typedef struct {
	int *pnData; //指向数据的指针
	int nSize; //当前堆中节点个数
} MyHeap;

int Insert(MyHeap *pHeap, int nData);
int IncreaseKey(MyHeap *pHeap, int nPos);
int PopMaxHeap(MyHeap *pHeap);

int main() {
	int i;
	MyHeap myHeap;
	//为结构成员分配存储空间
	//为什么不这么做? & malloc(sizeof(int) * 11);
	myHeap.pnData = (int *) malloc(sizeof(int) * 11);
	myHeap.nSize = 0;

	//向空二叉树添加10个节点
	for (i = 1; i <= 10; i++)
		Insert(&myHeap, i); //边插入,边调整

	//查看插入的内容
	for (i = 1; i <= 10; i++)
		cout << myHeap.pnData[i] << ' ';
	cout << endl;

	//查看未被利用的 myHeap.pnData[0]
	cout << "未被利用的 myHeap.pnData[0]: " << myHeap.pnData[0] << endl;

	//弹出大顶,查看堆内容
	for (i = 1; i <= 10; i++) {
		cout << "PopMax: " << PopMaxHeap(&myHeap) << ' ' << endl;
		for (int j = 1; j <= 10 - i; j++) // check list after pop
			cout << myHeap.pnData[j] << ' ';
		cout << endl;
	}
	cout << endl;

	return 0;
}

int Insert(MyHeap *pHeap, int nData) { //插入新节点
	++pHeap->nSize; //堆的节点数+1
	//索引号从1开始,方便操作;索引为0的地方浪费了?
	pHeap->pnData[pHeap->nSize] = nData;
	IncreaseKey(pHeap, pHeap->nSize);
	return 1;
}

//比较调整新插入节点的位置,向上渗透
int IncreaseKey(MyHeap *pHeap, int nPos) {
	while (nPos > 1) { //循环和其父节点比较
		int nMax = pHeap->pnData[nPos]; // temp
		int nParent = nPos / 2; //父节点号
		if (nMax > pHeap->pnData[nParent]) {
			pHeap->pnData[nPos] = pHeap->pnData[nParent];
			pHeap->pnData[nParent] = nMax;
			nPos = nParent;
		} else
			break;
	}
	return 1;
}

//返回堆中根节点的值,并删除根节点,删除后,还需调整堆
int PopMaxHeap(MyHeap *pHeap) {
	int nMax = pHeap->pnData[1];
	int nPos = 1; //当前节点
	int nChild = nPos * 2; //左孩子节点位置

	//最后一个节点赋值给根
	pHeap->pnData[nPos] = pHeap->pnData[pHeap->nSize];
	pHeap->nSize -= 1; //立即调整nSize

	//向下渗透
	int temp;
	//重点是循环条件设定 以及 下面的if条件
	//循环条件 nChild <= pHeap->nSize
	//if条件
	//nChild + 1 <= pHeap->nSize && temp < pHeap->pnData[nChild + 1]
	while (nChild <= pHeap->nSize) { //若左孩子存在
		temp = pHeap->pnData[nChild];

		//若右孩子存在,且比左孩子大
		if (nChild+1 <= pHeap->nSize && temp < pHeap->pnData[nChild+1]){
			nChild += 1;
			//temp里始终装左右孩子中的最大值,且nChild指针随之移动
			temp = pHeap->pnData[nChild];
		}

		//确定左右孩子大小后,大的那个若大于nPos元素,向下渗透
		if (pHeap->pnData[nPos] < temp) {
			pHeap->pnData[nChild] = pHeap->pnData[nPos];
			pHeap->pnData[nPos] = temp;
		}
		nPos = nChild; //重新调整 nPos
		nChild *= 2; //重新调整 nChild
	}
	return nMax;
}
1.3.3.2 Heap-Sort

未排序数据一个个放入堆,再从堆中取出.下面代码与1.3.3.1相比,修改了main,添加了void heap_sort(int * L, int len)及其在程序首部的函数声明,其他未变.

void heap_sort(int * L, int len);

int main() {
	int L[] = { 3, 38, 5, 44, 15, 36, 26, 27, 2, 46, 4, 19, 47, 48, 50 };
	int len = sizeof(L) / sizeof(L[0]);
	heap_sort(L, len); //测试
	return 0;
}

//未排序数据一个个放入堆,再从堆中取出
void heap_sort(int * L, int len) {
	MyHeap myHeap;
	//为结构成员分配存储空间
	myHeap.pnData = (int *) malloc(sizeof(int) * (len + 1));
	myHeap.nSize = 0;

	// 打印未排序的数组
	cout << "Unsorted array is: " << endl;
	for (int i = 0; i < len; i++)
		cout << L[i] << ' ';
	cout << endl;

	// 建堆
	for (int i = 1; i <= len; i++)
		Insert(&myHeap, L[i - 1]);

	// 输出排序结果
	cout << "sorted array by Heap-Sort is: " << endl;
	while (myHeap.nSize > 0)
		cout << PopMaxHeap(&myHeap) << ' ';
	cout << endl;
}

1.4 Merge-Sort

1.4.1 二路归并排序

  • Stable
  • Array
  • Average time complexity (O(nlogn)), worst case time complexity (O(nlogn)), best case time complexity (O(n))
  • Space complexity (O(n))

Idea: 两个有序序列,取队首(较小)元素送入新队列,新队列就已经排序了.

void merge(int *L, int lo, int mid, int hi) {
	if (lo >= hi) // 若1个元素或 lo > hi, 则无法合并
		return;
	int LL[hi - lo + 1]; //临时数组
	int i = lo, j = mid + 1, p = 0;

	// 正向思考,写全条件
	while (i <= mid && j <= hi)
		LL[p++] = L[i] < L[j] ? L[i++] : L[j++];

	while (i <= mid)
		LL[p++] = L[i++];

	while (j <= hi)
		LL[p++] = L[j++];

	// LL已经装满,需把数据依次送入 L[lo ... hi]
	for (i = lo, p = 0; i <= hi; i++, p++)
		L[i] = LL[p];
} //merge

void merge_sort(int *L, int lo, int hi) {
	if (lo >= hi)
		return; //递归终止条件
	int mid = (lo + hi) / 2;
	merge_sort(L, lo, mid);
	merge_sort(L, mid + 1, hi);
	merge(L, lo, mid, hi);
} //merge_sort

1.4.2 多路归并排序

原文地址:https://www.cnblogs.com/ZhongliangXiang/p/7357131.html