Algorithm in Practice - Sorting and Searching
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
0.1. Sequential-Search
// 无序数组顺序查找
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;
}
0.2. Binary-Search
针对下面数组,查找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
5 7 3 0 4 2 1 9 6 8
lo hi
1 2 3 0 4 5 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 多路归并排序
Algorithm in Practice - Sorting and Searching的更多相关文章
- Chp11: Sorting and Searching
Common Sorting Algo: Bubble Sort: Runime: O(n2) average and worst case. Memory: O(1). void BubbleSor ...
- CareerCup Chapter 9 Sorting and Searching
9.1 You are given two sorted arrays, A and B, and A has a large enough buffer at the end to hold B. ...
- 20162314 Experiment 3 - Sorting and Searching
Experiment report of Besti course:<Program Design & Data Structures> Class: 1623 Student N ...
- [Java in NetBeans] Lesson 15. Sorting and Searching.
这个课程的参考视频和图片来自youtube. 主要学到的知识点有: Build in functions in java.util.Collections Need to implement a co ...
- [转]Paging, Searching and Sorting in ASP.Net MVC 5
本文转自:http://www.c-sharpcorner.com/UploadFile/4b0136/perform-paging-searching-sorting-in-Asp-Net-mvc- ...
- Turbo Boyer-Moore algorithm
MySQL :: MySQL 8.0 Reference Manual :: 8.3.9 Comparison of B-Tree and Hash Indexes https://dev.mysql ...
- (转)Awesome Courses
Awesome Courses Introduction There is a lot of hidden treasure lying within university pages scatte ...
- 软件工程卷1 抽象与建模 (Dines Bjorner 著)
I 开篇 1. 绪论 II 离散数学 2. 数 (已看) 3. 集合 4. 笛卡尔 5. 类型 6. 函数 7. λ演算 8. 代数 9. 数理逻辑 III 简单RSL 10. RSL中的原子类型和值 ...
- 用python语言讲解数据结构与算法
写在前面的话:关于数据结构与算法讲解的书籍很多,但是用python语言去实现的不是很多,最近有幸看到一本这样的书籍,由Brad Miller and David Ranum编写的<Problem ...
随机推荐
- Python入门之迭代器/生成器/yield的表达方式/面向过程编程
本章内容 迭代器 面向过程编程 一.什么是迭代 二.什么是迭代器 三.迭代器演示和举例 四.生成器yield基础 五.生成器yield的表达式形式 六.面向过程编程 ================= ...
- Codeforces Round #436 (Div. 2) B. Polycarp and Letters
http://codeforces.com/contest/864/problem/B 题意: 给出一个字符串,要求找到一个集合S,使得从S中选出的所有数,在这些数的位置上的字母全部为小写且是不同的字 ...
- POJ-1753 Flip Game---二进制枚举子集
题目链接: https://vjudge.net/problem/POJ-1753 题目大意: 有4*4的正方形,每个格子要么是黑色,要么是白色,当把一个格子的颜色改变(黑->白或者白-> ...
- hdu1249 三角形分割平面---递推
题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1249 题目大意: 用N个三角形最多可以把平面分成几个区域? 思路: 知道了直线和折线分割平面的情况这 ...
- spring boot 系列之二:spring boot 如何修改默认端口号和contextpath
上一篇文件我们通过一个实例进行了spring boot 入门,我们发现tomcat端口号和上下文(context path)都是默认的, 如果我们对于这两个值有特殊需要的话,需要自己制定的时候怎么办呢 ...
- 华为防火墙USG5500-企业双ISP出口
需求:(1)技术部IP地址自动获取,网段为192.168.10.0/24,该部门访问Internet的报文正常情况下流入链路ISP1. 总经办IP地址自动获取,网段为192.168.20.0/24,该 ...
- 自动化运维:使用flask+mysql+highcharts搭建监控平台
1.前言 本来想做一个比较完善的监控平台,只需要做少许改动就可以直接拿来用,但是在做的过程中发现要实现这个目标所需的工作量太大,而当前的工作中对其需求又不是特别明显.所以就退而求其次,做了一个类似教程 ...
- 如何降低移动APP的开发成本
在当下竞争激烈的商业世界中,移动APP开发是您业务的有利补充.移动APP可通过吸引新客户和保留现有客户,帮助公司成功开展业务.定制一个属于自己公司的移动APP扮演着重要角色,手机APP可以说通过轻松处 ...
- [C#].Net Core 获取 HttpContext.Current 以及 AsyncLocal 与 ThreadLocal
在 DotNetCore 当中不再像 MVC5 那样可以通过 HttpContext.Current 来获取到当前请求的上下文. 不过微软提供了一个 IHttpContextAccessor 来让我们 ...
- CARVARS 圆形进度条
先看下效果 这是两个不同进度的进度条,由于carvas用找的只是id所以下面想多个进度条必须要根据class来找到id,所以我封装了一个进度条的函数,下面的代码直接复制粘贴就可以了 <!DOCT ...