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

 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 多路归并排序

Algorithm in Practice - Sorting and Searching的更多相关文章

  1. Chp11: Sorting and Searching

    Common Sorting Algo: Bubble Sort: Runime: O(n2) average and worst case. Memory: O(1). void BubbleSor ...

  2. 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. ...

  3. 20162314 Experiment 3 - Sorting and Searching

    Experiment report of Besti course:<Program Design & Data Structures> Class: 1623 Student N ...

  4. [Java in NetBeans] Lesson 15. Sorting and Searching.

    这个课程的参考视频和图片来自youtube. 主要学到的知识点有: Build in functions in java.util.Collections Need to implement a co ...

  5. [转]Paging, Searching and Sorting in ASP.Net MVC 5

    本文转自:http://www.c-sharpcorner.com/UploadFile/4b0136/perform-paging-searching-sorting-in-Asp-Net-mvc- ...

  6. Turbo Boyer-Moore algorithm

    MySQL :: MySQL 8.0 Reference Manual :: 8.3.9 Comparison of B-Tree and Hash Indexes https://dev.mysql ...

  7. (转)Awesome Courses

    Awesome Courses  Introduction There is a lot of hidden treasure lying within university pages scatte ...

  8. 软件工程卷1 抽象与建模 (Dines Bjorner 著)

    I 开篇 1. 绪论 II 离散数学 2. 数 (已看) 3. 集合 4. 笛卡尔 5. 类型 6. 函数 7. λ演算 8. 代数 9. 数理逻辑 III 简单RSL 10. RSL中的原子类型和值 ...

  9. 用python语言讲解数据结构与算法

    写在前面的话:关于数据结构与算法讲解的书籍很多,但是用python语言去实现的不是很多,最近有幸看到一本这样的书籍,由Brad Miller and David Ranum编写的<Problem ...

随机推荐

  1. Python入门之迭代器/生成器/yield的表达方式/面向过程编程

    本章内容 迭代器 面向过程编程 一.什么是迭代 二.什么是迭代器 三.迭代器演示和举例 四.生成器yield基础 五.生成器yield的表达式形式 六.面向过程编程 ================= ...

  2. Codeforces Round #436 (Div. 2) B. Polycarp and Letters

    http://codeforces.com/contest/864/problem/B 题意: 给出一个字符串,要求找到一个集合S,使得从S中选出的所有数,在这些数的位置上的字母全部为小写且是不同的字 ...

  3. POJ-1753 Flip Game---二进制枚举子集

    题目链接: https://vjudge.net/problem/POJ-1753 题目大意: 有4*4的正方形,每个格子要么是黑色,要么是白色,当把一个格子的颜色改变(黑->白或者白-> ...

  4. hdu1249 三角形分割平面---递推

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1249 题目大意: 用N个三角形最多可以把平面分成几个区域? 思路: 知道了直线和折线分割平面的情况这 ...

  5. spring boot 系列之二:spring boot 如何修改默认端口号和contextpath

    上一篇文件我们通过一个实例进行了spring boot 入门,我们发现tomcat端口号和上下文(context path)都是默认的, 如果我们对于这两个值有特殊需要的话,需要自己制定的时候怎么办呢 ...

  6. 华为防火墙USG5500-企业双ISP出口

    需求:(1)技术部IP地址自动获取,网段为192.168.10.0/24,该部门访问Internet的报文正常情况下流入链路ISP1. 总经办IP地址自动获取,网段为192.168.20.0/24,该 ...

  7. 自动化运维:使用flask+mysql+highcharts搭建监控平台

    1.前言 本来想做一个比较完善的监控平台,只需要做少许改动就可以直接拿来用,但是在做的过程中发现要实现这个目标所需的工作量太大,而当前的工作中对其需求又不是特别明显.所以就退而求其次,做了一个类似教程 ...

  8. 如何降低移动APP的开发成本

    在当下竞争激烈的商业世界中,移动APP开发是您业务的有利补充.移动APP可通过吸引新客户和保留现有客户,帮助公司成功开展业务.定制一个属于自己公司的移动APP扮演着重要角色,手机APP可以说通过轻松处 ...

  9. [C#].Net Core 获取 HttpContext.Current 以及 AsyncLocal 与 ThreadLocal

    在 DotNetCore 当中不再像 MVC5 那样可以通过 HttpContext.Current 来获取到当前请求的上下文. 不过微软提供了一个 IHttpContextAccessor 来让我们 ...

  10. CARVARS 圆形进度条

    先看下效果 这是两个不同进度的进度条,由于carvas用找的只是id所以下面想多个进度条必须要根据class来找到id,所以我封装了一个进度条的函数,下面的代码直接复制粘贴就可以了 <!DOCT ...