数据结构 c语言(严蔚敏) 总结 + 代码

第一章 基本绪论

基本概念和术语

  • 数据(Data):客观事物的符号表示,所有能够输入计算中并被计算机处理的符号的总称。
  • 数据元素(Data Element):数据的基本单位,在计算机中作为一个整体进行考虑和处理。
  • 数据项(Data Item): 是组成数据元素的、有独立含义的、不可再分的最小单位
  • 数据对象(Data Object): 是性质相同的数据元素的集合,是数据的子集。
  • 数据结构(Data Structure): 相互之间存在一种或特定多种关系的数据元素的集合。"结构"就是数据元素之间存在的关系。
  • 逻辑结构:从逻辑上描述数据,它与数据的存储无关,独立于计算机。2个要素:数据元素和关系
    • 集合结构:属于同一集合的关系。
    • 线性结构:一对一的关系。
    • 树结构:一对多的关系。
    • 图结构:多对多的关系。

在这里插入图片描述

  • 存储结构(物理结构):数据对象在计算机中的存储。即要存储数据元素的数据,也要存储元素之间的逻辑关系。

    • 顺序存储结构:借助元素在存储器上的相对位置来表示元素之间的关系。要求一片连续的空间。

    • 链式存储结构:无需一片连续的空间,但是为了表示元素之间的关系,需要添加一个指针字段,保存后继元素的存储地址

  • 数据类型(Data Type):是一个值的集合和定义在这个值集上的一组操作的总称。

  • 抽象数据类型(Abstract Data Type, ADT): 一般由用户定义,表示应用问题的数学模型,以及在这个模型上的一组操作的总称,包含数据对象、数据关系、基本关系

算法和算法分析

  • 算法(Algorithm):为了解决某类问题而规定的一个有限长的操作序列
    • 有穷性: 必须在执行有穷步后停止,且每一步都必须在有限时间内完成。
    • 确定性: 每种情况下所执行的操作必须是确定的。
    • 可行性:算法中的所有操作都可以通过已经实现的基本操作执行有限次完成。
    • 输入:0个或多个输入。
    • 输出:一个或多个输出。
  • 评估算法优劣的基本标准
    • 正确性
    • 可读性
    • 健壮性
    • 高效性
  • 时间复杂度(Time Complexity): 算法时间的量度。
  • 空间复杂度(Space Complexity):算法存储空间的量度。指算法实现时需要的辅助空间,与输入数据所需的存储量无关。

第二章 线性表

顺序存储

#include 
#include 
#include  
#define MAXSIZE 1005
#define OK 1
#define ERROR 0
typedef int Status;
using namespace std;
struct Book{
	char no[30];
	char name[30];
	double price;
	bool operator == (const Book &w) const {
		if (!strcmp(no, w.no)) return true;
		return false;
	} 
};
struct List {
	Book* elem; 
	int lenth; 
};
//初始化线性表 
Status initList(List &list) {
	list.elem = new Book[MAXSIZE];
	if (!list.elem) exit(0); //创建失败
	list.lenth = 0;
	return OK; 
} 
//获取线性表元素
Status getElem(List &list, int i, Book &elem) {
	if (i < 1 || i > list.lenth) return ERROR;
	elem = list.elem[i - 1];
	return OK;
} 
//获取某个元素在线性表中的位置 不存在返回0 
int locateElem(List &list, Book &elem) {
	for (int i = 0; i < list.lenth; i++) {
		if (elem == list.elem[i]) return i + 1;
	} 
	return 0;
} 
//添加元素
Status insertElem(List &list, Book &elem) {
	if (list.lenth == MAXSIZE) return ERROR;
	list.elem[list.lenth++] = elem;
	return OK;
} 
//添加元素通过位置
Status insertElem(List &list, int i, Book &elem) {
	if (i < 1 || i > list.lenth + 1) return ERROR;
	for (int j = list.lenth - 1; j >= i - 1; j--) {
		list.elem[j + 1] = list.elem[j];
	}
	list.elem[i - 1] = elem;
	list.lenth++;
	return OK;
} 
//删除指定位置的元素
Status deleteElem(List &list, int i) {
	if (i < 1 || i > list.lenth) return ERROR;
	for (int j = i - 1; j < list.lenth - 1; j++) list.elem[j] = list.elem[j + 1];
	list.lenth--;
	return OK;
} 
void output(Book &elem) {
	printf("no:%s name:%s price: %lf\n", elem.no, elem.name, elem.price);
}

List list;
int main() {
	initList(list);
	//添加元素
	Book books[100];
	for (int i = 1; i <= 3; i++) {
		Book book;
		scanf("%s%s%lf", book.no, book.name, &book.price);
		insertElem(list, i, book);
	} 
	for (int i = 0; i < list.lenth; i++) output(list.elem[i]);

	return 0;
} 

链式存储

  • 首元结点:是指链表中存储的第一元素结点

  • 头结点:是首元结点之前设置的一个结点,其指针域指向首元结点。

  • 链表是非随机存取的结构,要取得某个元素,必须从头指针出发进行寻找,也可称为顺序存取的结构。

  • ASL(Average Search Length) = n-1 / 2 (0, 1, 2,…,n-1)

单链表

#include 
#include 
#include  
#define MAXSIZE 1005
#define OK 1
#define ERROR 0
typedef int Status;
using namespace std;
struct Book{
	string no;
	string name;
	double price;
	bool operator == (const Book &w) const {
		if (no == w.no) return true; return false;
	} 
};
typedef struct Node {
	Book elem;
	Node *next;
}Node, *List;  
//初始化头节点 
Status initList(List &L) {
	L = new Node();
	L->next = NULL;
	return OK;
}
//获取链表中的元素
Status getElem(List &L, int i, Book &e) {
	Node *p = L->next; 
	int j = 1; //代表标号
	while (p && j < i) {
		p = p->next; j++;
	} 
	if (!p || j > i) return ERROR;
	e = p->elem;
	return OK;
} 
//单链表的插入, 插入位置为i 即ai-1 与ai之间  合法区间[1,n+1] 
Status insertElem(List &L, int i, Book &e) {
	Node *p = L; //初始从0开始 
	int j = 0;
	while (p && j < i - 1) { //在i-1的位置停下
 		p = p->next; j++;		
	} 
	if (!p || j > i - 1) return ERROR;
	Node *s = new Node(); 
	s->elem = e;
	//执行链接操作 
	s->next = p->next;
	p->next = s;
	return OK;
}
//输出链表中的元素 
void output(List &L) {
	Node *p = L;
	while (p->next) {
		p = p->next;
		cout << p->elem.no << " " << p->elem.name << " " << p->elem.price << endl;
	}
} 
//按值查找 查找失败返回NULL 
Node *locateElem(List &L, Book &e) {
	Node *p = L->next;
	while (p && !(p->elem == e)) p = p->next;
	return p;
} 
//删除指定位置的结点 [1, n]
Status deleteElem(List &L, int i) {
	Node *p = L; 
	int j = 0;
	while (p && j < i - 1) {
		p = p->next; j++;
	}
	//注意必须后面结点存在 
	if (!(p->next) || j > i - 1) return ERROR;
	Node *s = p->next;
	delete(s); //释放结点 
	p->next = p->next->next;
	return OK;
} 
//创建链表,并添加n个元素--前插法 
void createListHead(List &L, int n) {
	L = new Node(); 
	L->next = NULL;
	for (int i = 1; i <= n; i++) {
		Node *p = new Node();
		cin >> p->elem.no >> p->elem.name >> p->elem.price;
		p->next = L->next;
		L->next = p;
	}
} 
//创建链表,并添加n个元素--后插法 
void createListRear(List &L, int n) {
	L = new Node(); 
	L->next = NULL;
	Node *r = L;
	for (int i = 1; i <= n; i++) {
		Node *p = new Node();
		cin >> p->elem.no >> p->elem.name >> p->elem.price;
		r->next = p;
		p->next = NULL;
		r = p; //新的节点成为尾结点 
	}
} 
List L;
int main() {
//	initList(L); //初始化 
	createListRear(L, 3); 
	output(L);
	return 0;
} 

双向链表

#include 
#include 
#include  
#define MAXSIZE 1005
#define OK 1
#define ERROR 0
typedef int Status;
using namespace std;
struct Book{
	string no;
	string name;
	double price;
	bool operator == (const Book &w) const {
		if (no == w.no) return true; return false;
	} 
};
typedef struct Node {
	Book elem;
	Node *next;
	Node *prior; //直接前驱 
}Node, *List;  
//初始化头节点 
Status initList(List &L) {
	L = new Node();
	L->next = L;
	L->prior = L; 
	return OK;
}
int getLength(List &L) {
	Node *p = L;
	int j = 0;
	while (p->next != L) {
		j++; p = p->next;
	}
	return j;
}
//获取链表中的元素
Status getElem(List &L, int i, Book &e) {
	Node *p = L->next; 
	int j = 1; //代表标号
	while (p != L && j < i) {
		p = p->next; j++;
	} 
	if (p == L || j > i) return ERROR;
	e = p->elem;
	return OK;
} 
//单链表的插入, 插入位置为i 即ai-1 与ai之间  合法区间[1,n+1] 
Status insertElem(List &L, int i, Book &e) {
	Node *p = L; //初始从0开始      
	int len = getLength(L); 
	if (i < 1 || i > len + 1) return ERROR;
	while (i) {
		i--; p = p->next;
	}
	Node *s = new Node(); 
	s->elem = e;
	//执行链接操作 
	s->next = p;
	s->prior = p->prior;
	p->prior->next = s;
	p->prior = s; //最后一步连接p的前驱 
	return OK;
}
//输出链表中的元素 
void output(List &L) {
	Node *p = L;
	while (p->next != L) {
		p = p->next;
		cout << p->elem.no << " " << p->elem.name << " " << p->elem.price << endl;
	}
} 
//按值查找 查找失败返回NULL 
Node *locateElem(List &L, Book &e) {
	Node *p = L->next;
	while (p != L && !(p->elem == e)) p = p->next;
	return p;
} 
//删除指定位置的结点 [1, n]
Status deleteElem(List &L, int i) {
	Node *p = L; 
	int len = getLength(L);
	if  (i < 1 || i > len) return ERROR; 
	while (i) {
		i--; p = p->next;
	}	
	p->prior->next = p->next;
	p->next->prior = p->prior;
	delete(p);
	return OK;
} 
//创建链表,并添加n个元素--前插法 
void createListHead(List &L, int n) {
	L = new Node(); 
	L->next = L;
	L->prior = L;
	for (int i = 1; i <= n; i++) {
		Node *s = new Node(), *p = L->next;
		cin >> s->elem.no >> s->elem.name >> s->elem.price;
		s->next = p;
		s->prior = p->prior;
		p->prior->next = s;
		p->prior = s; 
	}
} 
List L;
int main() {
	initList(L); //初始化 
//	createListHead(L, 3); 
	for (int i = 1; i <= 3; i++) {
		Book book;
		cin >> book.no >> book.name >> book.price;
		insertElem(L, i, book);
	}
	output(L);
	return 0;
} 

顺序表和链表的比较

  • 存储空间的分配
    • 顺序表存储空间需要预先分配,元素的扩充受一定的限制,易造成存储空间浪费和空间溢出。链表不需要预先分配空间。
  • 存储密度的分配
    • 链表除了设置数据域来存储数据,还要额外增加指针域来保存元素之间关系的指针。
    • 当线性表的长度变化不大时,易用顺序表更加节省空间。否则变化大的话,会造成空闲区过多浪费存储空间,此时用链式存储更优。
  • 时间效率比较:
    • 顺序表是随机存取结构,可以在O(1)取得任何位置的元素。当需要进行大量的取值操作时,宜采用顺序表。
    • 对于链表,在确定插入和删除的结点后,插入和删除数据都无需移动元素,只需要修改对应的指针,时间复杂度为O(1)。故当进行大量的插入和删除操作时宜采用链式存储。

线性表的应用

有序链表的合并

#include 
#include 
#include 
using namespace std;
typedef struct Node {
	int data;
	Node *next;
}Node, *List;
//合并2个有序链表, 按照非递减进行排列 
void merge(List &C, List &A, List &B) {
	C = A; Node *p = C;
	Node *pa = A->next, *pb = B->next;
	while (pa && B) {
		if (pa->data <= pb->data) {
			p->next = pa;
			p = pa; 
			pa = pa->next;
		} else {
			p->next = pb;
			p = pb; 
			pb = pb->next;
		}
	} 
	p->next = pa ? pa : pb;
	delete(B);
}  
//创建链表,并添加n个元素--后插法 
void createListRear(List &L, int n) {
	L = new Node(); 
	L->next = NULL;
	Node *r = L;
	for (int i = 1; i <= n; i++) {
		Node *p = new Node();
		cin >> p->data;
		r->next = p;
		p->next = NULL;
		r = p; //新的节点成为尾结点 
	}
} 
void output(List &L) {
	Node *p = L;
	while (p->next) {
		p = p->next;
		printf("%d ", p->data);
	}
}
List A, B, C;
int main() {
	createListRear(A, 3);
	createListRear(B, 5);
	merge(C, A, B);
	output(C);
	return 0;
} 

多项式

#include 
#include 
#include 
using namespace std;
typedef struct Node {
	double coef; //系数
	int expo; //指数
	Node *next; 
}Node, *List; 
void createPoly(List &L, int n) {
	L = new Node();
	L->next = NULL;
	for (int i = 1; i <= n; i++) {
		Node *s = new Node(), *p = L->next, *pre = L;
		cin >> s->coef >> s->expo;
		//找到第一个比它大的
		while (p && p->expo < s->expo) {
			pre = p; 
			p = p->next;
		} 
		s->next = p;
		pre->next = s;
	}
}
void addPoly(List &C, List &A, List &B) {
	C = A;
	Node *pa = A->next, *pb = B->next, *pc = C;
	while (pa && pb) {
		if (pa->expo == pb->expo) {
			double sum = pa->coef + pb->coef;
			if (sum) {
				pa->coef = sum;
				Node *s = pb;
				pc->next = pa; pc = pc->next;
				pb = pb->next;
				pa = pa->next;
				delete(s);//在pb转移之后释放 
			} else {
				Node *s = pa; pa = pa->next; delete(s);
				s = pb;	pb = pb->next; delete(s);
			}
		} else if(pa->expo < pb->expo) {
			pc->next = pa; pc = pc->next; pa = pa->next;
		} else {
			pc->next = pb; pc = pc->next; pb = pb->next;
		} 
	}
	pc->next = pa ? pa : pb; 
}
void output(List &L) {
	Node *p = L;
	while (p->next) {
		p = p->next;
		if (p->next) cout<< p->coef << "x^" << p->expo << " + "; 
		else cout<< p->coef << "x^" << p->expo; 
	}
	cout << endl;
}
int main() {
	List A, B, C;
	createPoly(A, 3);
	output(A);
	createPoly(B, 3);
	output(B);
	addPoly(C, A, B);
 	output(C);
	return 0;
} 

第三章 栈与队列

案例应用

表达式求值

#include 
#include 
#include 
typedef long long ll;
using namespace std;
stack<char> OPTR;
stack<int> OPND;
char prt[7][8] = {
	{">><<<>>"},  
	{">><<<>>"},  
	{">>>><>>"},  
	{">>>><>>"},  
	{"<<<<<=>"},  
	{">>>>=>>"},             
	{"<<<<<<="}                                        
};
char id[8] = {"+-*/()\n"};      
//返回2个操作符大小关系 
char compare(char lop, char rop) {
	int ld = 0, rd = 0;
 	for (int i = 0; i < 7; i++) if (lop == id[i]) {ld = i;break;} 
 	for (int i = 0; i < 7; i++) if (rop == id[i]) {rd = i;break;} 
 	return prt[ld][rd];
}
void cal() {
	int b = OPND.top(); OPND.pop(); //先出来的为b 
	int a = OPND.top(); OPND.pop();
	char ch = OPTR.top(); OPTR.pop();
	if (ch == '+') OPND.push((a + b));
	if (ch == '-') OPND.push(a - b);
	if (ch == '*') OPND.push(a * b);
	if (ch == '/') OPND.push(a / b);
}
void evaluateExpression() {
	char ch;
	OPTR.push('\n');
	scanf("%c", &ch); 
	//当字符不是换行就执行操作,当遇到换行将OPTR所有操作清空 
	ll num = 0; //要添加的数 
	while (ch != '\n' || OPTR.top() != '\n') {
		if (ch >= '0' && ch <= '9') {
			num = num * 10 + (ch - '0');
			scanf("%c", &ch);
			continue;
		} 
		if (num) OPND.push(num), num = 0;
		switch (compare(OPTR.top(), ch)) {
			case '>':
				//大于就弹出2个数和操作符进行运算
				cal(); 
				break;
			case '<':
				//小于那么就将次操作符入栈,并输入下一个字符 
				OPTR.push(ch); scanf("%c", &ch);
				break;
			case '=':
				//等于代表括号匹配
				OPTR.pop(); //弹出匹配括号
				scanf("%c", &ch);
		}
	} 
	if (num) OPND.push(num);
	cout << OPND.top() << endl;
} 
int main() {
	freopen("expr.in","r",stdin);
    freopen("expr.out","w",stdout);
	evaluateExpression();
	return 0;
} 

第五章 树和二叉树

树的基本定义

  • 树(Tree): 是n个结点的有限集,n=0时为空树。当为非空树时:有且仅有一个称为根的结点。除根节点外其余结点可以划分为m个互不相交的有限集,其中每一个集合就是一棵树,称为根的子树(SubTree)。
  • 结点:树中的一个独立单元。
  • 结点的度:结点拥有的子树的数量。
  • 树的度:指树内各结点度的最大值。
  • 叶子:度为0的结点。也叫终端结点。
  • 非终端结点:度不为0的节点。除根节点外,终端结点也成为内部节点。
  • 双亲和孩子:结点的子树的根成为孩子,该结点成为孩子的双亲。
  • 层次:结点的层次从根节点开始为1。
  • 树的深度:最大层次成为树的深度。
  • 森林:m棵不相交的树的集合。对于树中的每个结点来说,其子树集合就是森林。

二叉树

性质

  • 第i层最多有2i-1 个结点。
  • 深度为k的二叉树最多有2i-1 - 1个结点。
  • 若度为0的点的个数为n0,度为2的个数为n2。 则n0 = n2 + 1。
    • 除了个根结点,每个结点都会发射出分支(<=2), 设度为1和度为2发射的总分支为B。 那么n(结点总数) = B + 1(根结点)。
    • 度为1的结点有一个分支,度为2的结点有2个分支,所以B = n1 + 2*n2
    • n = n1 + 2*n2 + 1 = n0 + n1 + n2
    • n0 = n2 +1
  • 满二叉树:每一层的结点数都达到最大数量。
  • 完全二叉树:当深度为k的二叉树中的每一个结点都与深度为k的满二叉树中从1到n标号的结点一一对应,那么这个二叉树为完全二叉树。
  • 具有n个结点的二叉树深度为 floor(logn) + 1。

根据遍历性质确定二叉树

  • 根据先序+中序 或 后序+中序能唯一确定一棵二叉树。
  • 如 中序:BDCEAFHG + 后序:DECBHGFA
    • 后序的最后一个字母为A,代表根,将中序分为左(BDCE) 右(FHG)
    • 然后继续后序倒数第二字母为F, 那么又将A的右子树F为根,划分为左(空) 右(HG)
    • 然后继续后序倒数第三字符为G,将G作为根,划分为左(H),右(空)
    • 直到后序取完所有字母,最终构成二叉树。
    • 在这里插入图片描述

代码

#include 
using namespace std;
typedef struct BiTNode {
	char data;
	BiTNode *lchild, *rchild;
}BiTNode, *BiTree; 
//通过输入先序序列创建二叉树 
void createBiTreeByPreOrder(BiTree &T) {
	char c;
	cin >> c;
	if (c != '#') {
		T = new BiTNode();
		T->data = c;
		createBiTreeByPreOrder(T->lchild); 
		createBiTreeByPreOrder(T->rchild); 
	} else {
		T = NULL;
	}
}
//对二叉树进行中序遍历 
void inOrderTraverse(BiTree &T) {
	if (!T) return;
	inOrderTraverse(T->lchild);
	cout << T->data;
	inOrderTraverse(T->rchild);
}
//复制二叉树
void copyTree(BiTree &T, BiTree &tem) {
	if (!T) {
		tem = NULL;
		return;
	}
	tem = new BiTNode();
	tem->data = T->data;
	copyTree(T->lchild, tem->lchild);
	copyTree(T->rchild, tem->rchild); 
} 
//获取二叉树的深度
int getDepth(BiTree &T) {	
	if (!T) return 0;
	int rdp = getDepth(T->lchild);
	int ldp = getDepth(T->rchild);
	return rdp > ldp ? rdp + 1 : ldp + 1;
} 
//获取二叉树的结点个数
int getNodeCount(BiTree &T) {
	if (!T) return 0;
	return getNodeCount(T->lchild) + 1 + getNodeCount(T->rchild); 
} 
//获取度为0的结点个数
int getNodeCountN0(BiTree &T) {
	if (!T) return 0;
	if (T->lchild == NULL && T->rchild == NULL) return 1;
	return getNodeCountN0(T->lchild) + getNodeCountN0(T->rchild);
} 
//获取度为1的结点个数
int getNodeCountN1(BiTree &T) {
	if (!T) return 0;
	bool v = (T->lchild && !T->rchild) || (T->rchild && !T->lchild);
	return getNodeCountN1(T->lchild) + getNodeCountN1(T->rchild) + (v ? 1 : 0);
} 
//获取度为2的结点个数
int getNodeCountN2(BiTree &T) {
	if (!T) return 0;
	bool v = T->lchild && T->rchild;
	return getNodeCountN2(T->lchild) + getNodeCountN2(T->rchild) + (v ? 1 : 0); 
} 
int main() {
	//ABC##DE#G##F### 
	BiTree T, tem; 
	createBiTreeByPreOrder(T);    //先序创建 
	inOrderTraverse(T); puts(""); //中序遍历 
	copyTree(T, tem); 
	inOrderTraverse(tem); puts("");
	cout << getDepth(T) << endl; //获取深度 
	cout << getNodeCount(T) << endl; //获取结点总数 
	cout << getNodeCountN0(T) << endl; //获取度为0的结点 
	cout << getNodeCountN1(T) << endl; 
	cout << getNodeCountN2(T) << endl; 
	return 0; 
} 	

线索二叉树

定义

  • 线索:指向结点前驱或后继的指针。
  • 如图中序为:a+b*c-d-e/f

在这里插入图片描述

算法步骤

  • 用pre表示前驱(全局变量):初始化将rchild 赋值为NULL。最开始代表一个空结点。p代表当前递归结点。也叫pre的后继。
  • 下面以中序为例:
    • 若p非空,递归调用左子树
    • 若p左孩子为空(代表可以用左指针域作线索),将lTag = 1(代表左指针域保存的是线索),p->lchild = pre, 若p左孩子非空,lTag = 0
    • 同理,若pre右孩子为空(代表可以用右指针域作线索),将rTag = 1(代表右指针域保存的是线索),pre->rchild = p, 若pre右孩子非空,rTag = 0
    • 最后将pre = p ; 递归调用p右子树。
Node *pre;
void inThread(Tree &p) {
	if (p) {
		inThread(p->lchild);
		//若p->lchild为空,则保存前驱
		if (p->lchild) {
			p->lTag = 0; //代表不为空 保存孩子 
		} else {
			p->lchild = pre;
			p->lTag = 1;
		}
		//同理pre
		if (pre->rchild) {
			pre->rTag = 0;
		} else {
			pre->rchild = p;
			pre->rTag = 1;
		}
		//pre变成p 成为新前驱
		pre = p;
		inThread(p->rchild); 
	}
} 
//带头节点的线索二叉树
void inThread(Tree &Th, Tree &T) {
	Th = new Node();
	Th->lTag = 0; //左边指向树的根结点,若为空则指向自己
	Th->rTag = 1; //右边指向最后一个结点,若T为空则指向自己 
	Th->rchild = Th; 
	if (!T) Th->lchild = Th;
	else {
		Th->lchild = T; 
		//初始应该为NULL 
		Th->rchild = NULL; pre = Th;
		inThread(T);
		pre->rchild = Th; pre->lTag = 1; 
		Th->rchild = pre; //pre这时候代表最后一个结点 
	}
}

查找前驱和后继

  • 中序线索二叉树
    • p指针所指结点的前驱:若p->lTag = 1,则p->lchild为其前驱,否则p的左子树最后一个结点为前驱
    • p指针所指结点的后继:若p->rTag = 1,则p->rchild为其后继,否则p的遍历右子树时的第一个结点,即右子树左下的结点。
  • 先序线索二叉树
    • 前驱:若p->lTag = 1,则p->lchild为其前驱,否则分为2种情况:
      • p是双亲的左孩子,那么根据先序遍历的顺序,它的前驱为双亲。
      • p是双亲的右孩子,它的前驱是双亲的左子树上先序遍历最后访问的节点,若左子树为空,则前驱就是双亲。
    • 后继:若p->rTag = 1,则p->rchild为其后继,否则分为左子树根,若左子树根不存在,则为右子树根。
  • 后序线索二叉树
    • 前驱: 若p->lTag = 1,则p->lchild为前驱。若p->lTag为0,那么根据后序是根最后访问的特性,它的前驱不是左子树根,就是右子树根。
      • 若p->rTag = 0, 代表左右子树都存在,那么p->rchild为其前驱。
      • 若p->rTag = 1, 代表左子树存在,右子树不存在,那么p->lchild为前驱。
    • 后继:
      • 若p为根结点,则无后继。
      • 若p是双亲的左孩子,且双亲无右子树,那么双亲是后继。
      • 若p是双亲的左孩子,且双亲有右子树,那么双亲的右子树中第一个访问的结点(左下的叶子结点)即是后继。
      • 若p是双亲的有孩子,则双亲是后继。

线索二叉树的遍历

  • 以带头结点的中序线索二叉树遍历为例:
    • 从头结点开始,到头结点结束。
    • 首先p结点指向根结点。
    • 若p为非空或遍历未结束时:
      • 沿p的左孩子往下,找到左孩子为空的结点。它就是中序的第一个结点。
      • 然后根据这个结点判断rTag的情况,若rTag=1直接访问其后继,重复直至rTag=0。
      • rTag=0代表这个结点有右子树,那么直接转向这个结点的右子树。
    • 直到回到头结点遍历结束。
//对中序线索二叉树进行遍历---带头节点 
void inOrderThreadTraverseWithHead(Tree &Th) {
	Node *p = Th->lchild; //最先指向根节点
	while (p != Th) { 
		//转向p的最左结点即为第一个中序结点
		while (p->lTag == 0) p = p->lchild;
		cout << p->data;
		//找p的后继 直接转向 
		while (p->rTag == 1 && p->rchild != Th) {
			p = p->rchild; 
			cout << p->data;
		}  
		//转向p的右子树 
		p = p->rchild;
	} 
}
//不带头节点
void inOrderThreadTraverseWithoutHead(Tree &T) {
	Node *p = T;
	while (p != NULL) {
		while (p->lTag == 0) p = p->lchild;
		cout << p->data;
		while (p->rTag == 1 && p->rchild != NULL) {
			p = p->rchild; 
			cout << p->data;
		}
		p = p->rchild;
	}
} 

代码

#include 
using namespace std;
typedef struct Node {
	char data;
	Node *lchild, *rchild;
	int lTag, rTag; //0代表指针域存储孩子,1代表存储线索 
} Node, *Tree;
//通过输入先序序列创建二叉树 
void createBiTreeByPreOrder(Tree &T) {
	char c;
	cin >> c;
	if (c != '#') {
		T = new Node();
		T->data = c;
		createBiTreeByPreOrder(T->lchild); 
		createBiTreeByPreOrder(T->rchild); 
	} else {
		T = NULL;
	}
}	
Node *pre;
void inThread(Tree &p) {
	if (p) {
		inThread(p->lchild);
		//若p->lchild为空,则保存前驱
		if (!p->lchild) {
			p->lchild = pre;
			p->lTag = 1;
		} else {
			p->lTag = 0; //代表不为空 保存孩子 
		}
		//同理pre
		if (!pre->rchild) {
			pre->rchild = p;
			pre->rTag = 1;
		} else {
			pre->rTag = 0;
		}
		//pre变成p 成为新前驱
		pre = p;
		inThread(p->rchild); 
	}
} 
//带头节点的线索二叉树
void inThread(Tree &Th, Tree &T) {
	Th = new Node();
	Th->lTag = 0; //左边指向树的根结点,若为空则指向自己
	Th->rTag = 1; //右边指向最后一个结点,若T为空则指向自己 
	Th->rchild = Th; 
	if (!T) Th->lchild = Th;
	else {
		Th->lchild = T; 
		//初始应该为NULL 
		Th->rchild = NULL; pre = Th;
		inThread(T);
		pre->rchild = Th; pre->lTag = 1; 
		Th->rchild = pre; //pre这时候代表最后一个结点 
	}
}
//对二叉树进行中序遍历 
void inOrderTraverse(Tree &T) {
	if (!T) return;
	inOrderTraverse(T->lchild);
	cout << T->data;
	inOrderTraverse(T->rchild);
}
//对中序线索二叉树进行遍历---带头节点 
void inOrderThreadTraverseWithHead(Tree &Th) {
	Node *p = Th->lchild; //最先指向根节点
	while (p != Th) { 
		//转向p的最左结点即为第一个中序结点
		while (p->lTag == 0) p = p->lchild;
		cout << p->data;
		//找p的后继 直接转向 
		while (p->rTag == 1 && p->rchild != Th) {
			p = p->rchild; 
			cout << p->data;
		}  
		//转向p的右子树 
		p = p->rchild;
	} 
}
//不带头节点
void inOrderThreadTraverseWithoutHead(Tree &T) {
	Node *p = T;
	while (p != NULL) {
		while (p->lTag == 0) p = p->lchild;
		cout << p->data;
		while (p->rTag == 1 && p->rchild != NULL) {
			p = p->rchild; 
			cout << p->data;
		}
		p = p->rchild;
	}
} 
int main() {
	Tree T, Th; 
	//-+a##*b##-c##d##/e##f##
	createBiTreeByPreOrder(T); 	 
	inOrderTraverse(T); puts("");
//	pre = new Node(); pre->rchild = NULL;
//  	inThread(T);
//	inOrderThreadTraverseWithoutHead(T);
	inThread(Th, T);
	inOrderThreadTraverseWithHead(Th); 
	return 0;
} 

树与森林

  • 森林的先序遍历和中序遍历可以转化为其二叉树的先序遍历和中序遍历。
  • 树的先序遍历和后序遍历可以转化为其二叉链表树的先序遍历和中序遍历。

哈夫曼树

定义

  • 哈夫曼树:又称最优树,是一类带权路径长度最短的树。
  • 路径:从一个结点到另一个结点之间的分支构成2个结点之间的路径。
  • 路径长度:路径上的分支数量
  • 权:是赋予实体的一个量,对实体的某个或某些属性的描述。
  • 树的路径长度:从根结点到每个结点的路径长度之和。
  • 结点的带权路径长度:结点的权值乘以结点到树根的路径长度
  • 树的带权路径长度:所有结点的带权路径长度之和。

构造算法

  1. 对于给定的n个权值{w1,w2,…wn},构造n棵只有根节点的二叉树,这n棵树构成一个森林F。
  2. 在森林中选取2个根节点权值最小的二叉树合并成一棵新的二叉树,根结点权值为2个根结点权值之和。在F中删除选出的2棵二叉树,并把新合并的二叉树加入森林。
  3. 重复2步骤直至森林中仅剩一棵二叉树即为哈夫曼树。
  • 构造哈夫曼时优先选择权值小的,这样保证离根的权值更大,是典型的贪心算法

代码

#include 
const int INF = 0x3f3f3f3f;
using namespace std;
typedef struct Node {
	int w, lchild, rchild, parent;
}Node, *HuffmanTree;
void selectMin(HuffmanTree &T, int n, int &l, int &r) {
	int w1 = INF, w2 = INF;
	for (int i = 1; i <= n; i++) {
		if (!T[i].parent) {
			if (T[i].w < w1) {
				w2 = w1; r = l;
				w1 = T[i].w; l = i;
			} else if(T[i].w < w2) {
				w2 = T[i].w; r = i;
			}
		}
	}
} 
void createHuffmanTree(HuffmanTree &T, int n) {
	//初始化条件
	int m = 2 * n - 1; //有2n-1个结点
	T = new Node[m + 1];
	for (int i = 1; i <= m; i++) {
		T[i].lchild = T[i].rchild = T[i].parent = 0;
	}
	for (int i = 1; i <= n; i++) cin >> T[i].w;
	//构造哈夫曼树
	int l, r; //代表选择的2个权值最小的根节点编号 
	for (int i = n + 1; i <= m; i++) {
		//选择根结点权值最少且无双亲的 
		selectMin(T, i - 1, l, r);
		T[i].lchild = l, T[i].rchild = r;
		T[l].parent = T[r].parent = i;
		T[i].w = T[l].w + T[r].w;
		cout << l <<":"<<T[l].w <<"===" << r <<":" << T[r].w<<endl;
	} 
}
int main() {
	//5 29 7 8 14 23 3 11
	HuffmanTree T;
	createHuffmanTree(T, 8);	
	for (int i = 8; i <= 15; i++) {
		cout << T[i].w << " ";
	}
	return 0;       
} 

哈夫曼编码

  • 前缀编码:任一编码都不是其他任何编码的前缀,则称编码为前缀编码。
#include 
const int INF = 0x3f3f3f3f;
using namespace std;
typedef struct Node {
	int w, lchild, rchild, parent;
}Node, *HuffmanTree;
void selectMin(HuffmanTree &T, int n, int &l, int &r) {
	int w1 = INF, w2 = INF;
	for (int i = 1; i <= n; i++) {
		if (!T[i].parent) {
			if (T[i].w < w1) {
				w2 = w1; r = l;
				w1 = T[i].w; l = i;
			} else if(T[i].w < w2) {
				w2 = T[i].w; r = i;
			}
		}
	}
} 
void createHuffmanTree(HuffmanTree &T, int n) {
	//初始化条件
	int m = 2 * n - 1; //有2n-1个结点
	T = new Node[m + 1];
	for (int i = 1; i <= m; i++) {
		T[i].lchild = T[i].rchild = T[i].parent = 0;
	}
	for (int i = 1; i <= n; i++) cin >> T[i].w;
	//构造哈夫曼树
	int l, r; //代表选择的2个权值最小的根节点编号 
	for (int i = n + 1; i <= m; i++) {
		//选择根结点权值最少且无双亲的 
		selectMin(T, i - 1, l, r);
		T[i].lchild = l, T[i].rchild = r;
		T[l].parent = T[r].parent = i;
		T[i].w = T[l].w + T[r].w;
	} 
}
string *HC; //用来保存每个编码的字符串 
void createHuffmanCode(HuffmanTree &T, int n) {
	HC = new string[n + 1]; //每个下标从1开始
	//从每个根结点出发 
	for (int i = 1; i <= n; i++) {
		int f = T[i].parent; //双亲 
		int s = i; //当前结点编号 
		while (f != 0) { //哈夫曼树的根结点无双亲 
			if (T[f].lchild == s) {
				HC[i].insert(0, "0"); //是左边分支 编码为0 
			} else {
				HC[i].insert(0, "1");				
			}
		 	s = f; f = T[s].parent; //继续往上 		
		}
	} 
}
string encode(string prestr) {
	string enc;
	for (int i = 0; i < prestr.length(); i++) {
		enc.append(HC[prestr[i] - 'a' + 1]);
	}	
	return enc; 
}
string str = "*abcdefgh"; //代表输入的字符 
string decode(HuffmanTree &T, string enc) {
	string dec;
	int m = 15;
	int s = m; //根节点编号 
	for (int i = 0; i < enc.length(); i++) {
		if (T[s].lchild == 0 && T[s].rchild == 0) {
			dec += str[s];
			s = m;
		}
		if (enc[i] == '0') {
			s = T[s].lchild;
		} else s = T[s].rchild;
	}
	return dec;
}
int main() {
	//5 29 7 8 14 23 3 11
	//a b c d e f g h  代表每个根结点代表的字符 
	HuffmanTree T;
	createHuffmanTree(T, 8);	
	for (int i = 9; i <= 15; i++) {
		cout << T[i].w << " " << T[i].parent << endl;
	}
	createHuffmanCode(T, 8);
	//输出每个根结点的编码 
	for (int i = 1; i <= 8; i++) {
		cout << str[i] << "-" << T[i].w << ":" << HC[i] << endl; 
	}
	//编码
	cout << "编码:" << encode("aabbccddeeffgghh") << endl; 
	//解码 
	cout << "解码:" << decode(T, encode("aabbccddeeffgghh")) << endl; 
	return 0;       
} 

综合应用

二叉树求解表达式

  • 利用第三章的栈求表达式值的思想。这里开EXPT栈来保存每个建立好的表达式的根结点,OPTR来保存输入的操作符。
  • 若输入的是数字,那么直接以它作为一棵树的根结点,压入EXPT栈中。
  • 若输入的是字符,与OPTR栈顶的字符进行比较
    • 如大于,那么从EXPT栈中弹出2个结点,将OPTR栈顶操作符弹出作为根结点,EXPT中第一个弹出的作为作为右子树,第二个弹出的作为左子树。最后将这个新形成的树压入EXPT。
    • 若小于,那么将这个操作符压入OPTR。
    • 若等于,代表是()匹配,那么直接将OPTR栈顶匹配的括号进行弹出。
#include 
#include 
using namespace std;
typedef struct Node {
	char ch;
	Node *lchild, *rchild; 
}Node, *Tree;
char prt[7][8] = {
	{">><<<>>"},
	{">><<<>>"},
	{">>>><>>"},
	{">>>><>>"},
	{"<<<<<=>"},
	{">>>>=>>"},
	{"<<<<<<="}
};
char id[8] = {"+-*/()#"};
char compare(char lop, char rop) {
	int ld, rd;
	for (int i = 0; i < 7; i++) if (id[i] == lop) {ld = i; break;}
	for (int i = 0; i < 7; i++) if (id[i] == rop) {rd = i; break;}
	return prt[ld][rd];
}
//创建表达式树保存在T中 
void creatExpTree(Tree &T) {
	stack<Tree> EXPT;
	stack<char> OPTR;
	OPTR.push('#');
	char ch;
	cin >> ch;
	while (ch != '#' || OPTR.top() != '#') {
		if (ch >= '0' && ch <= '9') {
			Tree root = new Node(); root->lchild = root->lchild = NULL;
			root->ch = ch;
			EXPT.push(root);
			cin >> ch;
			continue;
		}
		switch (compare(OPTR.top(), ch)) {
			case '<':
				//直接将这个字符压入OPTR
				OPTR.push(ch); cin >> ch;
				break;
			case '>':
				//弹出2个树进行合并
				Tree rt, lt, root;
				rt = EXPT.top(); EXPT.pop();  
				lt = EXPT.top(); EXPT.pop();
				root = new Node(); root->ch = OPTR.top(); OPTR.pop();  
				root->lchild = lt; root->rchild = rt;
				EXPT.push(root);
				break;
			case '=': 
				OPTR.pop(); cin >> ch;	
				break;	
		}
	}
	//最后栈顶的就是形成的表达式树 
	T = EXPT.top();
}
void inOrederTraverse(Tree &T) {
	if (T == NULL) return;
	inOrederTraverse(T->lchild);
	cout << T->ch;
	inOrederTraverse(T->rchild);
}
int expValue(Tree &T) {
	if (!T->lchild && !T->rchild) return (T->ch - '0');
	int a = expValue(T->lchild); 
	int b = expValue(T->rchild);
	switch (T->ch) {
		case '+':
			return a + b;
		case '-':
			return a - b;
		case '*':
			return a * b;
		case '/':
			return a / b;
	}
}
int main() {
	//(3+2)+(1)+4*(5-2)-8/4 
	Tree T;
	creatExpTree(T);
	inOrederTraverse(T);
	cout << endl << expValue(T) << endl;
	return 0;
} 

你可能感兴趣的:(数据结构 c语言(严蔚敏) 总结 + 代码)