您的位置:首页 > 理论基础 > 数据结构算法

数据结构基础笔记(一)【严蔚敏】

2015-08-29 14:23 501 查看

广义表

广义表相关概念:

◆ a1(表中第一个元素)称为表头

◆ 其余元素组成的子表称为表尾;(a2,a3,…,an)

◆ 广义表中所包含的元素(包括原子和子表)的个数称为表的长度。

◆ 广义表中括号的最大层数称为表深 (度)。

根据对表头、表尾的定义,任何一个非空广义表的表头可以是原子,也可以是子表, 而表尾必定是广义表。

只要广义表非空,都是由表头和表尾组成,即一个确定的表头和表尾就唯一确定一个广义表。

广义表数据结点定义:

typedef struct GLNode
{
int   tag ;     /*  标志域,为1:表结点;为0 :原子结点  */
union
{  elemtype value;     /* 原子结点的值域  */
struct
{  struct GLNode  *hp , *tp ;
}ptr ;   /*  ptr和atom两成员共用  */
}Gdata ;
} GLNode ;      /* 广义表结点类型  */


二叉树

满二叉树(Full Binary Tree)

一棵深度为k且有2^k-1个结点的二叉树称为满二叉树。

完全二叉树(Complete Binary Tree)

如果深度为k,由n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号从1到n的结点一一对应,该二叉树称为完全二叉树。

完全二叉树是满二叉树的一部分,而满二叉树是完全二叉树的特例。

####遍历

前序非递归遍历

思路:

1).访问当前结点,用栈暂存当前结点的右结点【如果不为空】;

2).如果当前结点的左结点不为空,则进入继续访问,否则从栈取结点访问,即返回1继续,如果栈为空则结束遍历;

vector<int> preorderTraversal(TreeNode* root) {
vector<int> ans;
if (!root) return ans;
stack<TreeNode*> stk_tree;
while (1){
ans.push_back(root->val);
if (root->right) stk_tree.push(root->right);
if (root->left) root = root->left;
else if (stk_tree.empty()) break;
else {
root = stk_tree.top(); stk_tree.pop();
}
}
return ans;
}


中序非递归遍历

思路:

1).若p不为空,p进栈,p=p->Lchild;

2).否则(即p为空),退栈到p,访问p所指向的结点;【栈为空则结束】

3).如果p的右结点不为空,p=p->Rchild ,转1;

vector<int> inorderTraversal(TreeNode* root) {
vector<int> ans;
if (!root) return ans;
stack<TreeNode*> s;
while (root) {
s.push(root);
root = root->left;
}
while (!s.empty()) {
TreeNode * t = s.top();
s.pop();
ans.push_back(t->val);
if (t->right) {
t = t->right;
while (t){
s.push(t);
t = t->left;
}
}
}
return ans;
}


后序非递归遍历

思路:

这里使用flag的栈对应表示结点属性,如果flag=1表示结点为父节点,进栈后再次出栈时可以直接访问;而如果flag=0表示该节点是右子节点,需要将该节点的子节点入栈,同时它本身的flag变为1.

1).若p不为空,p进栈【flag=1】,同时如果p的右结点不为空,右结点进栈【flag=0】,进入p=p->left;

2).从栈中取值,如果flag=1,说明该节点的子节点已经访问结束,该节点可以访问;否则进入1继续。【栈为空结束】

vector<int> postorderTraversal(TreeNode* root) {
vector<int> ans;
if (!root) return ans;
stack<TreeNode*> stk_tree;
stack<int> stk_flag;
while (root){
stk_tree.push(root);
stk_flag.push(1);
if (root->right) {
stk_tree.push(root->right); stk_flag.push(0);
}
root = root->left;
}
while (!stk_tree.empty()) {
root = stk_tree.top(); stk_tree.pop();
flag = stk_flag.top(); stk_flag.pop();
if (!flag) {
while (root){
stk_tree.push(root);
stk_flag.push(1);
if (root->right) {
stk_tree.push(root->right); stk_flag.push(0);
}
root = root->left;
}
}
else {
ans.push_back(root->val);
}
}
return ans;
}


在一条路径中,若没有重复相同的顶点,该路径称为简单路径

第一个顶点和最后一个顶点相同的路径称为回路(环)

对无向图G=(V,E),若任意vi,vj∈V,vi和vj都是连通的,则称图G是连通图,否则称为非连通图。若G是非连通图,则极大的连通子图称为G的连通分量

对有向图G=(V,E),若任意vi,vj∈V,都有以vi为起点, vj 为终点以及以vj为起点,vi为终点的有向路径,称图G是强连通图,否则称为非强连通图。若G是非强连通图,则极大的强连通子图称为G的强连通分量

最小生成树

普里姆(Prim)算法

算法思想(算法复杂度O(n^2)):

1). 若从顶点v0出发构造,U={v0},TE={};

2). 先找权值最小的边(u,v),其中u∈U且v∈V-U,并且子图不构成环,则U= U∪{v},TE=TE∪{(u,v)} ;

3). 重复⑵ ,直到U=V为止。则TE中必有n-1条边, T=(U,TE)就是最小生成树。

题目链接:#1097 : 最小生成树一·Prim算法

定义一维数组保存V-U中各顶点到U中顶点具有权值最小的边【lowcost】:如果lowcost为0表示已加入到U中,否则每轮都要V-U中更新各顶点的lowcost

struct {
int  adjvex;     /*   边所依附于U中的顶点   */
int  lowcost;    /*   该边的权值   */
}closedge
;


#include <stdio.h>
#include <string.h>
#define N 1002
#define INFINITY 10005;

struct { int adjvex; /* 边所依附于U中的顶点 */ int lowcost; /* 该边的权值 */ }closedge ;
int array

;

int prim(int n){
int i, j, min, v, ans = 0, idx;
//初始化数组【默认从v0开始】
for (i = 0; i < n; i++) {
closedge[i].adjvex = 0;
closedge[i].lowcost = array[i][0];
}
//n-1趟
for (j = 0; j < n - 1; j++) {
min = INFINITY;
//找到到U的最小权值的顶点
for (v = 0; v < n; v++) {
if (closedge[v].lowcost != 0 && closedge[v].lowcost < min) {
min = closedge[v].lowcost;
idx = v;
}
}
ans += min;
closedge[idx].lowcost = 0;//将该顶点加入U中
for (v = 0; v<n; v++)
if (array[v][idx]<closedge[v].lowcost) {
closedge[v].lowcost = array[v][idx];
closedge[v].adjvex = idx;
} /* 修改数组closedge
的各个元素的值 */
}
return ans;
}

int main()
{
int n, i, j;
scanf("%d", &n);
for (i = 0; i < n; i++)
for (j = 0; j < n; j++)
scanf("%d", &array[i][j]);
printf("%d\n", prim(n));
return 0;
}


注:prim算法稍加修改即可变为Dijkstra算法。【lowcost表示到各个顶点到给定单点的最短距离。】

克鲁斯卡尔(Kruskal)算法

思想:

设G=(V, E)是具有n个顶点的连通网,T=(U, TE)是其最小生成树。初值:U=V,TE={} 。c

对G中的边按权值大小从小到大依次选取。

1). 选取权值最小的边(vi,vj),若边(vi,vj)加入到TE后形成回路,则舍弃该边(边(vi,vj) ;否则,将该边并入到TE中,即TE=TE∪{(vi,vj)} 。

2). 重复1),直到TE中包含有n-1条边为止。

题目链接:#1098 : 最小生成树二·Kruscal算法

先将所有边排序,然后遍历判定是否边的两个顶点是否在一个集合,如果在则会形成回路,舍弃之;否则将改边加入TE。【判定两个点是否在一个集合用并查集算法】

#include<iostream>
#include<algorithm>
using namespace std;

struct edge{
int u;
int v;
int len;
};

int cmp(const edge a, const edge b) {
return a.len < b.len;
}

#define M 1000005
#define N 100005
edge e[M];
int pos
;                         //记录各个节点在哪个集合中。

void init(int n) {
for (int i = 1; i <= n; i++)    pos[i] = i;
}

int find(int x) {
if (x == pos[x]) return x;
else {
pos[x] = find(pos[x]);
return pos[x];
}
}

int kruskal(int n, int m) {
int sum = 0,count = 1;
for (int i = 0; i < m; i++) {
int fx = find(e[i].u);
int fy = find(e[i].v);
if (fx != fy) {
sum += e[i].len;
count++;
if (count == n) break;
pos[fx] = fy;
}
}
return sum;
}

int main() {
int n, m;
while (cin >> n >> m) {
init(n);                            //初始化并查集
for (int i = 0; i < m; i++) cin >> e[i].u >> e[i].v >> e[i].len;
sort(e, e + m, cmp);
cout << kruskal(n, m) << endl;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: