540 likes | 764 Views
程序设计实习. 第九讲 链表和二叉树 http://ai.pku.edu.cn/cpp2010. 链 表. 1) 链表的结构和特点 2) 单链表的操作:插入、删除算法. 单链表. 单链表的概念: 单链表( singly linked list) 是一种简单的链表表示,也称为线性链表。是由结点组成。 一个结点由两个域组成: ①数据域 ②指向下一结点的指针域. 特点 每个元素 ( 表项 ) 由结点 ( Node ) 构成 。. 线性结构. 结点可以不连续存储 表可扩充. 单链表中的插入与删除
E N D
程序设计实习 第九讲 链表和二叉树 http://ai.pku.edu.cn/cpp2010
链 表 1) 链表的结构和特点 2) 单链表的操作:插入、删除算法
单链表 单链表的概念: 单链表(singly linked list)是一种简单的链表表示,也称为线性链表。是由结点组成。 一个结点由两个域组成: ①数据域 ②指向下一结点的指针域
特点 • 每个元素(表项)由结点(Node)构成。 线性结构 • 结点可以不连续存储 • 表可扩充
单链表中的插入与删除 在一个线性单链表中插入结点的情况有三种
带表头结点的单链表 表头结点位于表的最前端,本身不带数据,仅标志表头。 设置表头结点的目的是统一空表与非空表的操作,简化链表操作的实现。
从带表头结点的单链表中删除第一个结点 q = p→link; p→link = q→link; delete q; if ( p→link == NULL ) last = p;
带表头结点的单链表的实现 //节点数据结构 struct LNode { int data; struct LNode * next; }; //链表数据结构 struct LinkList { LNode * pHead; //表头指针 LNode * pCurrent; //当前节点指针 int nLength; //表中节点数目(不含头节点) }; //每个该结构的变量代表一张链表
用来对链表进行操作的函数: 初始化(创建头节点): bool InitList ( LinkList * pThis ); 销毁整个链表,包括销毁头节点: bool DestroyList (LinkList * pThis); 删除全部节点,但头节点保留: bool ClearList(LinkList * pThis); 判断链表是否为空: bool IsEmpty (LinkList * pThis); 取链表长度: int GetLength (LinkList * pThis); 获取指向第position个节点的指针(position从1开始算) LNode * GetNode(LinkList * pThis, int position); 取值为elem的第一个元素的位置(返回0则没找到) int LocateElem(LinkList * pThis,int elem);
用来对链表进行操作的函数: 将第 position 个节点的值设为 newData bool SetNodeData(LinkList * pThis,int position, int newData); 获取第 position 个节点的值 bool GetNodeData(LinkList * pThis,int position, int &data); 将一个值为data的节点插入到第beforeWhich个节点前面 (beforeWhich的取值是 1 到 pThis->nLength + 1) bool InsertNode(LinkList * pThis,int beforeWhich, int data); 删除第position 个节点 bool DeleteNode(LinkList * pThis,int position); 使第一个节点成为当前节点 void Rewind(LinkList * pThis); 取当前节点的值,并移动到下一个节点 bool GetNextNodeData( LinkList * pThis, int * pData)
//初始化,分配一个头节点。 bool InitList( LinkList * pThis ) { if (!( pThis->pHead = new LNode)) { return false; } pThis->pHead->next = NULL; pThis->nLength = 0; pThis->pCurrent = pThis->pHead->next ; return true; } //销毁链表。 bool DestroyList(LinkList * pThis) { if (!ClearList( pThis)) { return false; } delete pThis->pHead; return true; }
//使第1个节点成为当前节点 void Rewind(LinkList * pThis) { pThis->pCurrent = pThis->pHead->next ; } //取当前节点数据,并移动到下一个节点 bool GetNextNodeData( LinkList * pThis, int * pData) { if( pThis->pCurrent == NULL ) return false; * pData = pThis->pCurrent->data; pThis->pCurrent= pThis->pCurrent->next ; return true; } //判断链表是否为空。若为空,返回true,否则返回false。 bool IsEmpty(LinkList * pThis) { if ( pThis->pHead->next == NULL) return true; return false; }
//返回链表中 的 当前节点数。 int GetLength(LinkList * pThis) { return pThis->nLength; } //将链表清空,释放当前所有节点(不包括头节点)。 bool ClearList(LinkList * pThis) { if (pThis->pHead == NULL) { return false; } LNode *pTemp = NULL; while (pThis->pHead->next != NULL) { pTemp = pThis->pHead->next; pThis->pHead->next = pTemp->next; delete pTemp; } pThis->nLength = 0; return true; }
//得到指定位置节点的数据。 //节点索引从1到listLength。 bool GetNodeData(LinkList * pThis,int position, int &data) { LNode *pTemp = GetNode(pThis,position); if (!pTemp) return false; data = pTemp->data; return true; }
//将position指定的节点内的数据设置为newData。 //第一个有效节点的position为1。 bool SetNodeData(LinkList * pThis,int position, int newData) { LNode *pTemp = GetNode(pThis,position); if (! pTemp) return false; pTemp->data = newData; return true; }
//在链表中插入一个节点。 //插入的位置由beforeWhich指定,新节点插入在beforeWhich之前 //beforeWhich的取值在1到ListLength+1之间。 bool InsertNode(LinkList * pThis,int beforeWhich, int data) { if (beforeWhich < 1 || beforeWhich > (pThis->nLength + 1)) { return false; } LNode *pTemp = GetNode(pThis, beforeWhich - 1); if (!pTemp) return false; LNode *newNode = new LNode; newNode->data = data; newNode->next = pTemp->next; pTemp->next = newNode; pThis->nLength++; return true; }
//删除一个指定的节点 , 节点位置由position指定。 //positon的值从1到listLength。 //若链表为空或指定的节点不存在则返回false。 bool DeleteNode(LinkList * pThis,int position) { if (position < 1 || position > pThis->nLength) { return false; } LNode *pTemp = GetNode(pThis,position - 1); if (!pTemp) return false; LNode *pDel = NULL; pDel = pTemp->next; pTemp->next = pDel->next; delete pDel; pThis->nLength--; return true; }
//得到指定位置节点的指针。 LNode * GetNode( LinkList * pThis,int position) { LNode *pTemp = NULL; int curPos = -1; pTemp = pThis->pHead; while (pTemp != NULL) { curPos++; if (curPos == position) break; pTemp = pTemp->next; } if (curPos != position) return 0; return pTemp; }
//定位与指定数据相等的数据节点。 //如果在当前链表中已经存在该数据则返回该数据节点的位置。 //若不存在这样的节点则返回0。 int LocateElem(LinkList * pThis,int elem) { LNode *pTemp = NULL; int curIndex = 1; pTemp = pThis->pHead->next; while ((pTemp != NULL) && (pTemp->data != elem)) { pTemp = pTemp->next; curIndex++; } if (pTemp == NULL) return 0; return curIndex; }
int main(){ LinkList MyList; //要再使用一张链表,则可以定义 LinkList MyList2; InitList ( & MyList); InsertNode(& MyList, 1, 10);InsertNode( & MyList, 2, 20); InsertNode(& MyList, 3, 30);InsertNode(& MyList, 4, 40); cout << GetLength(& MyList) << endl; int dataTemp = 0; for (int i = 1; i <= GetLength(& MyList); i++) { GetNodeData(& MyList,i, &dataTemp); cout << dataTemp << endl; } if (SetNodeData(& MyList,3, 50)) { cout <<"DONE\n"; } else { cout << "Failed\n"; }
Rewind( & MyList); while (GetNextNodeData( & MyList,&dataTemp)) cout << dataTemp << endl; if (DeleteNode(& MyList,4)) { cout <<"DONE\n"; } else cout << "Failed\n"; for (i = 1; i <= GetLength(& MyList); i++) { GetNodeData(& MyList,i, &dataTemp); cout << dataTemp << endl; } cout << LocateElem(&MyList,40) << endl; DeleteNode(&MyList,LocateElem( & MyList,50)); DeleteNode(&MyList,LocateElem( & MyList,20)); Rewind( & MyList); while (GetNextNodeData( & MyList,&dataTemp)) cout << dataTemp << endl; return 0; }
输出: 4 10 20 30 40 DONE 10 20 50 40 DONE 10 20 50 0 10
静态链表结构 利用数组定义,运算过程中存储空间大小不变 avil 代表下一个空的元素的位置
静态链表结构 静态链表适用于没有指针的程序设计语言 或既需要依据下标快速随机存取元素,又需要 维持另一种先后关系的情况
循环链表 (Circular List) 循环链表是单链表的变形。 循环链表最后一个结点的 link 指针不 为 0 (NULL),而是指向了表的前端。 为简化操作,在循环链表中往往加入表头结点。 循环链表的特点是:只要知道表中某一结点的地址,就可搜寻到所有其他结点的地址。
循环链表的示例 带表头结点的循环链表
单链表的应用举例 - 多项式相加 在多项式的链表表示中每个结点增加了一个数据成员link,作为链接指针。 • 优点是: • 多项式的项数可以动态地增长,不存在存储溢出问题。 • 插入、删除方便,不移动元素。
多项式链表的相加 设多项式的最高阶数为maxdegree当前的最高阶为n,各个项按指数递增的次序,从0~n排列。这里举出两种不同的表示方法: 第一种表示方法:建立一个具有maxdegree+1个元素的静态数组coef来存储多项式的系数: float coef[maxdegree+1]; //多项式的系数数组 特点:直观,但是空间浪费
多项式链表的相加 第二种表示方法:利用链表来表示多项式。它适合与项数不定的多项式,对于项数在运算过程中动态增长的多项式,不存在存储溢出的问题。其次对某些非零系数项,在执行加法后变成零系数项,这就需要在结果多项式中删去这些项,利用链表可以很方便的修改指针以完成这种插入和删除操作。
多项式链表的相加 AH = 1 - 10x6 + 2x8 +7x14 BH = - x4 + 10x6 - 3x10 + 8x14 +4x18
树和二叉树 树tree的定义 (1) 无结点的树 空树 (2) 非空树 仅有一个根结点 其余结点分为若干 互不相交的子树
A ········································根 3度 D B C H I J ·············叶 0度 E F G K L M 2个3度点 A,D 2个2度点 B,E 2个1度点 C,H 7个叶 F,G,I,J,K,L,M
A ·············································第一层 ······························第二层 D B C ···············第三层 H I J E F G K L ····································第四层 M 树的深度为4
二叉树 每个结点至多有两棵子树,1度或2度 A C B F G D E H I J
二叉树 设有n个2度点 则有n+1个叶片 A C B F G D E H I J 第i层结点至多2i-1个(i从1开始算) 深度为k,结点至多2k-1个
满二叉树 深度为k,结点数2k-1,每层结点数都最大的树 A C B F G D E H J K L M N O I
完全二叉树 满二叉树去掉最下层最右边若干结点 A C B F G D E H J K L M N O I 满二叉树也是完全二叉树
完全二叉树 n个结点,深度 k=[log2n] +1 A n个结点,深度为k: 2k-1-1<n≤2k-1 2k-1 ≤ n <2k k-1 ≤ log2n <k k-1 = [log2n] k = [log2n] +1 C B F G D E H I J K L M 1个结点深度为1 2-3个结点深度为2 4-7个结点深度为3 8-15个结点深度为4
完全二叉树 结点i的左子结点是2i 右子结点是2i+1 (i从1开始算) 1 3 2 6 7 4 5 8 9 10 11 12 13 结点i的父结点是[i/2] 完全二叉树可以用一维数组存储
二叉树的链式存储 树结点
A C B E F D G H
二叉树的遍历 先序遍历 PreOrderTraverse 先访问根结点 再访问左子树 后访问右子树 中序遍历 InOrderTraverse 先访问左子树 再访问根结点 后访问右子树 后序遍历 PostOrderTraverse 先访问左子树 再访问右子树 后访问根结点
假设二叉树的节点形式为: struct CharNode { char data; CharNode * left; CharNode * right; }; 构建如图的二叉树 并用三种方法遍历,最后 销毁该树 A C B F D E G H I
先序遍历 void PreOrder (CharNode * root, void (*visit)( char & item) ) { if (root) { visit(root->data); // visit the node PreOrder (root->left, visit); // descend left PreOrder (root->right, visit); // descend right } } pa A C B void PrintChar(char & c) { cout<< c;} F D E 则 Preorder(pa,PrintChar); 输出:ABDEGCFHI G H I
中序遍历 void MiddleOrder (CharNode * root, void (*visit)( char & item) ) { if (root) { MiddleOrder (root->left, visit); visit(root->data); MiddleOrder (root->right, visit); } } A C B F D E DBGEACHFI G H I