本手册原创整理 教程参考MOOC 邓俊辉老师的《数据结构》
写在前面的话:
上联:自学是门手艺,下联:记录是块招牌, 横批:四海为家
计算机系统,计算机语言分类,程序开发过程,补码原码啥的傻傻搞不清,强推清华 郑莉老师的《C++程序设计基础》~
面向对象思想是工程项目的核心,继承与派生、多态性、泛型程序设计是核心章节!仍强推清华的《面向对象》~
要学好机器学习,数据结构是基础!根据斯宾浩斯遗忘曲线,要时常复习!温故而知新!
△基础知识还是多上网课,少自己瞎琢磨,此记做日后的学习警醒!
辅助教材:
《C++程序设计基础》清华大学出版社 郑莉
《深入理解计算机系统》
主要从时间复杂度T(n)和空间复杂度分析 S(n)。
对于处理规模为n的数据的算法,一般算法只分析T(n), 用渐进上界O( f( n ) )表示,但在递归算法中往往要计算时间成本O(?)
for( i= 1; i < n; 1 << i);
// or
do {
n ++; } while( 2 == ( n * n ) % 5)
** 循环、递归也可能是O(1)
O(2^n):
_int64 Fib_basic( int n) {
return (2 > n ) ? (_int64) n : fib( n - 1) + fib( n - 2); }
O(n): 改进上述的Fib(), 用上楼问题的算法解决
_int64 Fib_updated( int n) {
_int 64 fun1 = 0; fun2 = 1;
while (0 < n --) {
fun2 += fun1; fun1= fun2 - fun1; }
return fun1;
}
动态规划:最长公共子序列(LCS)
求两个字符串 A = “immaculate”, B = “computer” 最长公共子序列的长度(A,B)
(1)dynamic programming (recommand)
自底向迭代,避免运算大量重复子问题,最大运算量Θ(n * m)
L C S ( A [ 0 , i ] , B [ 0 , j ] ) = { 0 i f i = 0 or j = 0 L C S ( A [ 0 , i − 1 ] , B [ 0 , j − 1 ] ) + 1 i f A [ i ] = B [ j ] a n d L C S ( A [ 0 , i ] , B [ 0 , j − 1 ] ) + 1 ⩾ L C S ( A [ 0 , i − 1 ] , B [ 0 , m ] ) m a x { L C S ( A [ 0 , i − 1 ] , B [ 0 , j ] ) , L C S ( A [ 0 , i ] , B [ 0 , j − 1 ] ) } i f n ≠ m LCS( A[0, i], B[0, j]) = \begin{cases} 0& \text{ $if$ $i$ = 0 or j = 0 } \\ LCS( A[0, i - 1], B[0, j - 1]) + 1& \text{ $if$ $A[i] = B[j] $ $and$ $LCS( A[0, i ], B[0, j - 1]) + 1 \geqslant LCS( A[0, i - 1], B[0, m ])$} \\ max \{LCS( A[0, i - 1], B[0, j ]),LCS( A[0, i ], B[0, j - 1])\} & \text{ $if$ $n \neq m$ } \end{cases} LCS(A[0,i],B[0,j])=⎩⎪⎨⎪⎧0LCS(A[0,i−1],B[0,j−1])+1max{ LCS(A[0,i−1],B[0,j]),LCS(A[0,i],B[0,j−1])} if i = 0 or j = 0 if A[i]=B[j] and LCS(A[0,i],B[0,j−1])+1⩾LCS(A[0,i−1],B[0,m]) if n=m
/*求A[0, n] 和 B[0, m] 的LCS(最长公共子序列)*/
//LCS函数
string LCS(const string &A, const string &B) {
//两字符串大小
unsigned n = A.size();
unsigned m = B.size();
//定义二维 n*m LCS长度表
vector<vector<int> > LCS_length(n + 1, vector<int>(m + 1, 0));
string LCS; //用string即可满足动态长度要求
//i == 0 || j == 0, LCS_length = 0
//定义时已完成
//从取A[0]开始,与B的子序列对比,记录LCS长度
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
//A[i] = B[j]时, LCS_length[i][j] = LCS_length[i - 1][j - 1] + 1
if (A[i - 1] == B[j - 1]) {
//i,j 从1开始,故要-1
LCS_length[i][j] = LCS_length[i - 1][j - 1] + 1;
//LCS = LCS + A[i - 1]; //errro
}
//A[i] != B[j]时, LCS_length[i][j] = max(LCS_length[i - 1][j] , LCS_length[i][j - 1])
else
LCS_length[i][j] = (LCS_length[i][j - 1] > LCS_length[i - 1][j] ?
LCS_length[i][j - 1] : LCS_length[i - 1][j]);
}
}
//输出LCS长度表
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= m; j++)
cout << LCS_length[i][j] << ' ';
cout << endl;
}
//返回LCS(A, B)的长度
return LCS;
}
(2)decrease - and - conquer(not recommand)
自顶向下,会产生大量重复子问题。当n = m 时,LCS(A[0, n], B[0, m])到 LCS(A[0, 0], B[0, 0])会重复计算n 个子问题,最大运算量Θ(2^n)
L C S ( A [ 0 , n ] , B [ 0 , m ] ) = { 0 i f n == 0 or m == 0 L C S ( A [ 0 , n − 1 ] , B [ 0 , m − 1 ] ) i f n == m m a x { L C S ( A [ 0 , n − 1 ] , B [ 0 , m ] ) , L C S ( A [ 0 , n ] , B [ 0 , m − 1 ] ) } i f n != m LCS( A[0, n], B[0, m]) = \begin{cases} 0& \text{ $if$ $n$ == 0 or m == 0 } \\ LCS( A[0, n - 1], B[0, m - 1])& \text{ $if$ n == $m$ } \\ max \{LCS( A[0, n - 1], B[0, m ]),LCS( A[0, n ], B[0, m - 1])\} & \text{ $if$ n != $m$ } \end{cases} LCS(A[0,n],B[0,m])=⎩⎪⎨⎪⎧0LCS(A[0,n−1],B[0,m−1])max{ LCS(A[0,n−1],B[0,m]),LCS(A[0,n],B[0,m−1])} if n == 0 or m == 0 if n == m if n != m
//代码待补!
using namespace std; //千万别忘了
左结合: a *= b -> first b then a * b
右结合:a && b -> first a then b
#pragma once
typedef int Rank; //秩
#define DEFAULT_CAPACITY 50 //默认初始容量 50
template <typename T> class Vector {
protected:
Rank _size; int _capacity; T* _elem; //规模、容量、数据区指针(未初始化)
void shrink(); //空间太大缩容
public:
//constructor
Vector(int capacity = DEFAULT_CAPACITY, int size = 0, T e = 0) {
//assert size < DEFAULT_CAPACITY
_elem = new T[_capacity = capacity]; //动态分配内存
for (_size = 0; _size < size; _elem[_size++] = e); //所有元素初始化为0/NULL
}
//read
Rank size() const {
return _size; } //获取规模
//read & write
T& operator[] ( Rank r ) {
return _elem[r]; }; //重载[], vector[r]返回对应元素
const T& operator[] (Rank r) {
return _elem[r]; } const ; //重载[], 针对常vector, 功能同上
Rank insert( Rank r, T const& e ); //插入元素
int uniquify(); //有序去重
};
具体成员函数实现见代码分享
提取码:ch21
//list类
#pragma once
#include"listNode.h" //include ListNode类
template <typename T> class List {
//List类模板
private: //size of elements, header_pointer, tailer_pointer
int _size = 0; ListNodeDirect(T) header; ListNodeDirect(T) trailer;
protected:
void init(); //创建空列表
int clear(); //清空列表
void copyNodes ( ListNodeDirect(T) pointer, int n ); //复制从pointer所指节点开始的 n 个节点
//排序方法
void insertionSort( ListNodeDirect(T) pointer, int n ); //插入排序:从pointer所指节点开始的 n 个节点
void selectionSort( ListNodeDirect(T) pointer, int n ); //选择排序:从pointer所指节点开始的 n 个节点
void mergeSort( ListNodeDirect(T) pointer, int n ); //归并排序:从pointer所指节点开始的 n 个节点
public:
//constructor
List() {
init(); } //default
List( List<T> const& L ); //copy whole List
List( List<T> const& L, Rank r, int n ); //copy List[r, r + n)
List( ListNodeDirect(T) pointer, int n ); //copy 从pointer->node 开始的n个
//destructor
~List();
//read
Rank size() const {
return _size; } //get rank(常函数)
bool empty() const {
return _size <= 0; } //isEmpty(常函数)
Rank nodeRank( T const& e ); //get rank of node
ListNodeDirect(T) fist() const {
return header->succ; } //get first node
ListNodeDirect(T) last() const {
return tailer->succ; } //get last node
//search
ListNodeDirect(T) find( T const& e, int n, ListNodeDirect(T) p ) const; //无序区间查找
ListNodeDirect(T) search( T const& e, int n, ListNodeDirect(T) p) const; //有序区间查找
ListNodeDirect(T) search( T const& e ) const {
return search( e, _size, tailer )}; //有序全区查找
ListNodeDirect(T) searchMax( ListNodeDirect(T) p, int n ); //全区查找最大
//read & write
//insert
ListNodeDirect(T) insertAsSucc( T const& e ); //插入为后继
ListNodeDirect(T) insertAsPred( T const& e ); //插入为前驱
ListNodeDirect(T) insertAsFirst( T const& e ); //插入为首节点
ListNodeDirect(T) insertAsLast( T const& e ); //插入为末节点
ListNodeDirect(T) insertAfter( ListNodeDirect(T) position, T const& e ); //插入为后继
ListNodeDirect(T) insertBefore( ListNodeDirect(T) position, T const& e ); //插入为前驱
//remove
T remove( ListNodeDirect(T) position ); //删除位置p处节点
//reverse
void reverse(); //前后倒置
};
#include "list_implementation.h"
具体成员函数实现见代码分享
提取码:fhj0
调用栈原理:
在执行2进制程序时,后台会调用栈(call stack)
每调用一次函数,都会重复四部曲:压入栈并创建帧—> 记录返回地址—> 记录局部变量、传入参数—>记录前一帧起始地址
每return一次(return给自己,可能白给或被利用),栈都会弹出栈顶,并返回前一帧的地址
括号匹配问题
/*代码验证: 代码4.4 括号匹配算法*/
//paren.h
#include
using namespace std;
//删除表达式exp[lo, hi]不含()的前、后缀
void trim(const char exp[], int& lo, int& hi) {
while ((lo <= hi) && (exp[lo] != '(') && (exp[lo] != ')')) lo++;
while ((lo <= hi) && (exp[hi] != '(') && (exp[hi] != ')')) hi--;
}
//从exp[lo, hi]中切分出局部匹配区间
int divide(const char exp[], int lo, int hi) {
//mi从lo开始遍历, crc表示已扫描区间的 (num("(") - num(")"))的差
int mi = lo, crc = 1;//初始化mi读入'(' , 故crc初始化为1
//遍历,直到找到区间 or mi越界
while ((0 < crc) && (++mi < hi)) {
/*邓老师,请看这里!*/
if(exp[mi] == ')') crc--; if (exp[mi] == '(') crc++; } //计算左、右括号数的差
cout << "crc:"<< crc << endl;
return mi; //确定切分点
}
bool paren(const char exp[], int lo, int hi) {
trim(exp, lo, hi); if (lo > hi) return true; //删除exp[lo, hi]不含()的前、后缀
if (exp[lo] != '(' || exp[hi] != ')') return false; //起始区间不匹配
int mi = divide(exp, lo, hi); //确定切分点
if (mi > hi) return false; //mi越界,则crc一定!0
return paren(exp, lo + 1, mi - 1) && paren(exp, mi + 1, hi);//递归检查左右exp[]
}
//以上为原配方
//*********************************************************************************
//以下为加了个“=”的配方
int divide_add(const char exp[], int lo, int hi) {
//mi从lo开始遍历, crc表示已扫描区间的 (num("(") - num(")"))的差
int mi = lo, crc = 1;//初始化mi读入'(' , 故crc初始化为1
//遍历,直到找到区间 or mi越界
while ((0 < crc) && (++mi <= hi)) {
/**************邓老师,我改了这里!***************/
if (exp[mi] == ')') crc--; if (exp[mi] == '(') crc++;
} //计算左、右括号数的差
cout << "crc:" << crc << endl;
return mi; //确定切分点
}
bool paren_add(const char exp[], int lo, int hi) {
trim(exp, lo, hi); if (lo > hi) return true; //删除exp[lo, hi]不含()的前、后缀
if (exp[lo] != '(' || exp[hi] != ')') return false; //起始区间不匹配
int mi = divide_add(exp, lo, hi); //确定切分点
if (mi > hi) return false; //mi越界,则crc一定!0
return paren_add(exp, lo + 1, mi - 1) && paren_add(exp, mi + 1, hi);//递归检查左右exp[]
}
问题详见邓俊辉老师的《数据结构》P 92
引用传递
定义: funciton( type const& ref_name)
实例:function( int_a ) ; // int const& ref_name = int_a
地址传递
定义: funciton( type* pointer_name)
实例:function( &int_a ) ; // int* pointer_name = &int_a
function( array_a ); // int* pointer_name = array_a
#define ClassName(T) ClassName*
int main(){
ClassName(int) object = new ClassName<int>(); //调用ClassName的default constructor, 返回int型模板类指针
/*对象指针必须初始化才能使用*/
}
线索二叉树
参考:
1.彻底理解线索二叉树
2.代码见邓公讲义05-F4–中序遍历:前驱与后继
string person //string类 person
person.resize(3); //person为长度3的字符串
int person_weight; //权值
//read data
//input: AAA 10
scanf("%3s %d", &person[0], &edge_weight);
//person: AAA, edge_weight: 10
#include
vector< vector<int> > persons(5, vector<int>(0));
//创建二维vector, 实际vector里无任何元素
//普通二叉树模板:
#pragma once
#include"BTNode.h" //BinTree node
template <typename T> class BST {
protected:
int _size; //树规模
BinTreeNodeDirect(T) _root; //根节点
virtual int updateHeight ( BinTreeNodeDirect(T) x ); //更新节点高度
void updateHeightAndAbove(BinTreeNodeDirect(T) x ); //更新节点及祖先高度
public:
int size() const {
return _size; } //total node number
bool empty() const {
return !_root; } //is BinTree empty
BinTreeNodeDirect(T) search(const T& e); //查找
bool insert(const T& e); //插入
bool remove(const T& e); //删除
};
#include"BinTree_IsAndHas.h"
#pragma once
//二叉排序树模板:
#pragma once
#include"BSTNode.h" //BinTree node
template <typename T> class BST {
protected:
int _size; //关键码总数
BinTreeNodeDirect(T) _root;
BinTreeNodeDirect(T) _hot; //BTree::search()最后访问的非空(除非树空)的节点位置
public:
int size() const {
return _size; } //total node number
bool empty() const {
return !_root; } //is BinTree empty
BinTreeNodeDirect(T) search( const T& e ); //查找
bool insert (const T& e ); //插入
bool remove ( const T& e); //删除
};
#include"BinTree_IsAndHas.h"
具体成员函数实现见代码分享
提取码:1iwy
题目描述:从 N 个城市, M条道路中生成连接N个城市的最小生成树,并输出树权
input :
6 10 //line1: num_cities num_roads
0 1 4 //line2 ~ M + 1: id_ct1 id_ct2 weightE[id_ct1][id_ct2]
0 4 1
0 5 2
1 2 1
1 5 3
2 3 6
2 5 5
3 4 5
3 5 4
4 5 3
11
//Prim.h
//Prim算法——只能用于求最小生成树,不能求路径
#ifndef PRIM
#define PRIM
#include
#include //使用fill()函数
using namespace std;
#define INF 10000000 //infinity
#define CAPACITY 510 //arrary capacity
//given
int num_cities, num_roads, id_start, id_end; //line 1 given
int weightE[CAPACITY][CAPACITY]; //line3 give, matrix
//required
int opt_WeightTree = 0; //min pathWeight
//assistance
bool IsVisited[CAPACITY] = {
false };
int SetToV[CAPACITY]; //the optWeight of edge from V to Set
//#define HaveEdge( id_pre, id_ct ) (weightE[id_pre][id_ct] != INF)
#define NewSetToV(id_pre, id_ct) ( weightE[id_pre][id_ct] < SetToV[id_ct])
#define UpdatWeightTree(MIN) (opt_WeightTree += MIN)
#define UpdateSetToV(id_pre, id_ct) ( SetToV[id_ct] < weightE[id_pre][id_ct])
int Prim() {
fill(SetToV, SetToV + CAPACITY, INF); //initialize
SetToV[id_start] = 0; //任取0放入集合Set
for (int id = 0; id < num_cities; id++) {
//traverse n vertexs
//每次找前驱时initialize
int id_pre = -1, MIN = INF;
for (int j = 0; j < num_cities; j++) {
//try to find pre
if (!IsVisited[j] && SetToV[j] < MIN) {
//unVisited & the closest city to Set
id_pre = j; //get id of city
MIN = SetToV[j]; //update closest value
}
}
//cannot find
if (id_pre == -1) return; // traverse next vertex
//find
IsVisited[id_pre] = true; //mark
UpdatWeightTree(MIN);
//优化各点到Set距离
for (int id_ct = 0; id_ct < num_cities; id_ct++) {
if (!IsVisited[id_ct] && NewSetToV(id_pre, id_ct)) //unVisited & reachable
UpdateSetToV(id_pre, id_ct);
}
}
return opt_WeightTree;
}
#endif // !PRIM
//待更新,含并查集知识
//Dijkstra + DFS, 也可以单独用Dijkstra
#ifndef DIJKSTRA_DFS
#define DIJKSTRA_DFS
#include
#include
#include
using namespace std;
#define MAX 510 //array capacity
#define INF 100000000 //infinity
//given
int num_cities, num_ways, id_start, id_des; //define variables for input in line1
int weightDist[MAX][MAX], weightCost[MAX][MAX]; //define 2 weighted graph
//assistance
bool IsVisited[MAX] = {
false }; //check vertexs which are visited or not
#define NewOptPath(id_pre, id_ct) ( Dis_optPath[id_pre] + weightDist[id_pre][id_ct] < Dis_optPath[id_ct] ) //is there a smaller total distance
#define UpdateOptDist(id_pre, id_ct) ( Dis_optPath[id_ct] = Dis_optPath[id_pre] + weightDist[id_pre][id_ct] ) //update total distance
#define SameOptPath(id_pre, id_ct) ( Dis_optPath[id_pre] + weightDist[id_pre][id_ct] == Dis_optPath[id_ct] ) //is there a equal total distance but different path
//calculate
int Dis_optPath[MAX], Cos_optPath = INF; //optDistance of each city (from id_start) & Cost of optPath (from id_start to id_des)
vector<int> pre[MAX]; //closest predecessor of each city
vector<int> tempPath, optPath; //temporary path & opt path
void Dijkstra() {
//initial Distance & Cost of optPath
fill(Dis_optPath, Dis_optPath + MAX, INF);
Dis_optPath[id_start] = 0; //默认 id_start--->id_start 的total distance 为0
//n个顶点依次作为id_pre
for (int i = 0; i < num_cities; i++) {
int id_pre = -1, MIN = INF; //initialize, easy to find isolated city
for(int id_ct = 0; id_ct < num_cities; id_ct++) //find a vertex
//search for unVisited & min of Dis_optPath
if (!IsVisited[id_ct] && Dis_optPath[id_ct] < MIN) {
id_pre = id_ct;
MIN = Dis_optPath[id_ct];
}
if (id_pre == -1) return; //cannot find
//find
IsVisited[id_pre] = true; //mark
for (int id_ct = 0; id_ct < num_cities; id_ct++) {
//遍历n个顶点,check 以id_pre为pred时是否要优化
if (!IsVisited[id_ct] && weightDist[id_pre][id_ct] < INF) {
//unvisited & reachable vertex
if (NewOptPath(id_pre, id_ct)) {
UpdateOptDist(id_pre, id_ct);
pre[id_ct].clear(); //clear predecessor of id_ct
pre[id_ct].push_back(id_pre); //update closest predecesspr
}
else if( SameOptPath(id_pre, id_ct) )
pre[id_ct].push_back(id_pre); //update predecesspr directly
}
}
}
}
void DFS( int id_ct ) {
//iteration base
if (id_ct == id_start) {
tempPath.push_back(id_ct); //add id_ct in tempPath
//calculate tempCost of tempPath
int tempCost = 0; //initialize
for (int i = tempPath.size() - 1; i > 0; i--) {
//add cost from back to forth
int idV = tempPath[i], idVNext = tempPath[i - 1];
tempCost += weightCost[idVNext][idV];
}
//is there a smaller optCost
if (tempCost < Cos_optPath) {
Cos_optPath = tempCost; //update Cos_optPath
optPath = tempPath; //update optPath
}
tempPath.pop_back(); //tempPath的首节点出栈
return;
}
tempPath.push_back(id_ct); //入栈
for( unsigned i = 0; i < pre[id_ct].size(); i++) //每个前驱遍历其所有optPath
DFS( pre[id_ct][i] ); //顺序选一条optPath深入,直到遇到leaf
tempPath.pop_back(); //每check完一个tempPath上一个节点, 节点出栈
}
#endif // !DIJKSTRA_DFS
/*
#include"A1030 Travel Plan.h"
int main() {
//input line1
scanf("%d %d %d %d", &num_cities, &num_ways, &id_start, &id_des);
//input line2~M+1 matrix
//initialize
fill(weightDist[0], weightDist[0]+ MAX * MAX, INF); //weightDist[][] = INF
fill(weightCost[0], weightCost[0] + MAX * MAX, INF); //weightCost[][] = INF
for (int i = 0; i < num_cities; i++) //self-reachable
weightDist[i][i] = 0, weightCost[i][i] = 0;
//input
int id_ct1 = 0, id_ct2 = 0;
for (int i = 0; i < num_ways; i++) {
scanf("%d %d", &id_ct1, &id_ct2);
scanf("%d", &weightDist[id_ct1][id_ct2]);
scanf("%d", &weightCost[id_ct1][id_ct2]);
weightDist[id_ct2][id_ct1] = weightDist[id_ct1][id_ct2]; //directed weightDist graph
weightCost[id_ct2][id_ct1] = weightCost[id_ct1][id_ct2]; //directed weightCost graph
}
//调用算法
Dijkstra(); //1.记录最短路径每层的最优前驱集合(有多少条Dist_optPath可以到达该层)
DFS( id_des ); //calculate Cost_optPath based on Dist_optPath
//output
//line1: cities from id_start--->id_des
for (int i = optPath.size() - 1; i >= 0; i--) //从栈顶开始输出
//optPath.size() == 0 ???d
printf("%d ", optPath[i]); //不要加“&”!!!
//line2: distance & cost
printf("%d %d\n", Dis_optPath[id_des], Cos_optPath); //不要加“&”
return 0;
}
*/
-----------------------我是有底线的---------------------------