1顺序表&链表 A.顺序表 基本结构
1 2 3 4 5 6 7 8 typedef struct SeqList { ElemType elem[MAXSIZE]; int last; } SeqList, LList, *LListPtr;
关于last last=-1表示空表,last+1是实际元素个数
因为last是按照角标来的 插入 1 2 3 if (i < 1 || i > L->last + 2 ) return false ;
插 入:在前面插入
插入的范围:i∈[1,last+ 2 ]
last+2是在最后一个后面插入
插入从后向前 移动
1 2 3 4 5 for * (int k = L->last; k >= i; --k) L->elem[k + 1 ] = L->elem[k]; L->elem[i - 1 ] = e; ++L->last;
复杂度O(n)
1 2 3 4 5 6 7 for * (int k = L->last; k >= i; --k) L->elem[k + 1 ] = L->elem[k]; L->elem[i - 1 ] = e; ++L->last;
删除 1 2 3 if * (i < 1 || i > L->last + 1 ) *return * false ;
删 除:删除指定节点
删除的范围:i∈[1,last+1]
删除从后向前移动
1 2 3 4 for * (int k = i - 1 ; k <= L->last; ++k) L->elem[k] = L->elem[k + 1 ]; --L->last;
复杂度O(n)
注意:在长度为n的顺序表中的的末尾位置 上插入/删除一个元素,其算法时间复杂度为O(1)。
1 2 3 4 5 for (int k = i - 1 ; k <= L->last; ++k) L->elem[k] = L->elem[k + 1 ]; --L->last;
!特别需要注意的是边界条件
在顺序表中,逻辑上相邻的两个元素物理存储上也一定也相邻。
线性表采用顺序存储,必须占用一段地址连续的存储单元。
线性表在顺序存储时,查找第i个元素的时间同i 的值无关。
顺序表结构适宜进行随机访问
顺序存储方式的优点是存储密度大
顺序存储中,插入和删除的效率比较低。
插入和删除操作是线性表的基本操作。这两种操作在数组中不常使用因为数组本身不能进行插入和删除,因为数组的长度是不可变的。(在某些语言中)
若某线性表最常用的操作是存取任一指定序号的元素和在最后进行插入和删除运算,则利用(顺序表)存储方式最节省时间。
练习题
假设删除长度为n的顺序表中的每个元素的概率相同,则删除一个元素平均要移动的元素个数是$(n-1)/2$。
在长度为n的顺序表中删除第i(1<=i<=n)个位置上的元素,需要移动的元素个数为$n-i$。
在长度为n的顺序表中的第i( 1 =< i <= n+1 )个位置上插入一个元素,需要移动的元素个数为$n-i+1$。
B.链表 ①普通链表 1 2 3 4 5 6 7 typedef struct _node { ElemType data; struct _node *next ; } Node, *NodePtr, LList, *LListPtr;
插入与删除 插入删除须从1开始,若有n个元素,
插入位置有效位置为 1~n+1
;删除位置有效位置为 1~n
后插
初始位置 p = &L->head/L k=1;
k表示节点的标,与i同,无需加减1.
插入判断条件(可以一直移动到最后一个node的后一个,才会出现null)
1 2 3 4 5 6 7 while * ( p != NULL && k < i) { p = p->next; ++k; } if (p == NULL ) return false ;
注:当i=1时,插入时恰好k=1。没有移动。
删除判断条件
1 2 3 4 5 while * (p->next != NULL && k < i) { p = p->next; ++k; } *if * (p->next == NULL ) *return * false ;
注意是p->next不是p了,因为插入可以在插入后面插入,但删除不行
②循环链表 插入和删除
1 2 3 4 5 6 7 8 NodePtr p = L; int k = 1 ; while (p->next != L && k < i) { p = p->next; ++k; } if (p->next == L && k < i) *return * false ;
p->next == L
表示链表循环又到头了
③双向链表
初始化 (*L)->next=(*L)->prior=NULL;
判断条件(与其他同)
1 2 3 4 5 6 7 while * (p != NULL && k < i) { p = p->next; ++k; } *if * (p == NULL && k < i) *return * false ;
插入 1 2 3 4 5 6 7 8 9 10 11 12 13 if * (p->next == NULL ) { p->next =s; s->next=NULL ; s->prior=p; } *else * { p = p->next; s->prior=p->prior; p->prior=s; s->prior->next=s; s->next=p; }
删除 1 2 3 4 5 6 7 8 9 10 11 p=p->next; p->prior->next=p->next; p->next->prior=p->prior; free (p);
链表插入、删除不需要移动元素;所需存储空间与线性表程度成正比;不必事先估计存储空间
链表添加头结点的目的是,简化操作,使得在表头和中间位置对链表的操作统一化。不一定是为了存储链表的长度。
顺序表的每个结点只能是一个基本类型,而链表的每个结点可以是一个构造类型。
栈和队列 栈 LIFO:先进后出
栈的两个结构 顺序栈
1 2 3 4 5 typedef struct { StackElementType elem[Stack_Size]; int top; } SeqStack, Stack, *StackPtr;
链式栈
1 2 3 4 typedef struct node { StackElementType data; struct node *next ; } LinkStackNode, *LinkStackNodePtr, LinkStack, Stack, *StackPtr;
弹栈与出栈 PUSH
1 2 3 temp->data = x; temp->next = S->next; S->next = temp;
POP
1 2 3 *x = temp->data; S->next = temp->next; free (temp);
但两者的接口相同 Push/Pop(StackPtr S, StackElementType x)
顺序表有“栈顶指针”,链表也有临时指针
异常返回
队列 FIFO:后进后出
链表结构 顺序表结构(循环) 判空判满 1 2 3 4 5 6 7 8 判断空 Q->front==Q->rear; 判断满 Q->front==(Q->rear+1 )%MAXSIZE;
注意:front的上一个单元一直未被用到 。所以会浪费一个单元
串 串的定义 1 2 3 4 typedef struct { CHAR ch[MAXLEN]; int len; } String, *StringPtr;
插入时串的下标 –代表s;|代表t
1 2 3 4 pos max -----||||----- A B A: pos+t->len B:s->len+t->len-1
1 2 3 4 pos max ------||||---------------- A B A POS+t->len B max-1
1 2 pos max ------|||||||||||||||||---
串的匹配 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int StrIndex (SString s,int pos, SString t) { int i, j, start; if (t.len==0 ) return (0 ); start=pos; i=start; j=0 ; while (i<s.len && j<t.len) if (s.ch[i]==t.ch[j]) { i++; j++; } else { start++; i=start; j=0 ; } if (j>=t.len) return (start); else return (-1 ); }
块链串 定义如下
1 2 3 4 5 6 7 8 9 10 typedef struct _block { char ch[BLOCK_SIZE]; struct _block *next ; } Block; typedef struct { Block *head; Block *tail; int len; } BLString;
读取关键
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 sub->head = p; while (pos > 0 ) { if (a == BLOCK_SIZE) { a = 0 ; q = q->next; } a++; pos--; slen--; } q->ch[a]=... while (...) { if (a == BLOCK_SIZE) { a = 0 ; q = q->next; } }
树与二叉树 基本概念 度 ☺结点的度 :树中一个结点的子树的个数称为该结点的度。
☺树的度 :树中各结点的度的最大值称为树的度,通常将度为m的树称为m次树或者m叉树
高度 树中结点的最大层次称为树的高度(或树的深度)。一般包含根节点
二叉树的性质 在二叉树的第i层上至多有$2^i-1$个结点(i≥1)
深度为k的二叉树至多有$2^k-1$个结点(k≥1)
对任意一棵二叉树T,若叶结点数为$n0$,而其度数为2 的结点数为$n2$,则满足 $n0= n2+1$
具有n个结点的完全二叉树的深度为:$\lfloor log_{2}^n \rfloor +1$
对于具有n个结点的完全二叉树,如果按照从上到下和从左到右的顺序对二叉树中的所有结点从1开始顺序编号,则对于任意的序号为 i 的结点有:
① 若 i = 1, 则 i 无双亲结点若 $i >1$, 则 i 的双亲结点为$\lfloor i/2 \rfloor$
② 若 2i > n, 则 i 无左孩子若 $2i≤n$, 则 i 结点的左孩子结点为$ 2i $
③ 若 2i +1 > n ,则 i 无右孩子若$ 2i +1≤n $, 则 i 的右孩子结点为$ 2i+1 $
二叉树的遍历 由遍历确定二叉树
非递归算法 先/中/后 序访问 基于栈
栈是递归的神奇
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 void Preorder (BiTree root) { Stack s; init_stack(&s); if (root == NULL ) return ; BiTree p=root; while (p != NULL || !is_empty(&s)) { if (p != NULL ) { push(&s, p); visit_node(p); p = p->left; } else { pop(&s, &p); p = p->right; } } return ; } void InOrder (BiTree root, CALLBACK Visit) { Stack s, *S = &s; BiTree p; InitStack(&S); p = root; while (p != NULL || !IsEmpty(S)) { if (p != NULL ) { Push(S, &p); p = p->LChild; } else { Pop(S, &p); Visit(p->data); p = p->RChild; } } ClearStack(S); } void PostOrder (BiTree root, CALLBACK Visit) { BiTNode *p, *q; Stack s, *S = &s; q = NULL ; p = root; InitStack(&S); while (p != NULL || !IsEmpty(S)) { if (p != NULL ) { Push(S, &p); p = p->LChild; } else { GetTop(S, &p); if (p->RChild==NULL || p->RChild ==q) { Visit(p->data); Pop(S, &p); q = p; p = NULL ; }else { p=p->RChild; } p = p->RChild; } } ClearStack(S); }
层级访问 基于队列 ,同一级别的节点加入队列。
先进先出,所以同一层级必须紧接着出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void LayerOrder (BiTree tree, CALLBACK visit) { Queue q, *Q = &q; BiTree p; InitQueue(Q); EnterQueue(Q, tree); while (!IsEmpty(Q)) { DeleteHead(Q, &p); visit(p->data); if (p->LChild != NULL ) { EnterQueue(Q, p->LChild); } if (p->RChild != NULL ) { EnterQueue(Q, p->RChild); } } ClearQueue(Q); }
递归题目 查找标签值相同的node 1 2 3 4 5 6 7 8 9 10 11 12 BiTree WhereIs (BiTree root, char node) { if (root==NULL ) return NULL ; if (root->data.tag==node) return root; BiTree p=WhereIs(root->LChild,node); if (p!=NULL ) return p; p=WhereIs(root->RChild,node); if (p!=NULL ) return p; return NULL ; }
两棵树是否结构相似 1 2 3 4 5 6 bool IsLike (BiTree t1, BiTree t2) { if (t1==NULL &&t2==NULL ) return true ; else if (t1==NULL ||t2==NULL ) return false ; return IsLike(t1->LChild,t2->LChild)&&IsLike(t1->RChild,t2->RChild); }
进阶:如果是镜像那么return IsLike(t1->LChild,t2->RChild)&&IsLike(t1->RChild,t2->LChild);
先序+中序确定二叉树 1 2 3 4 5 6 7 8 9 10 11 12 13 14 BiTree _Create(char *pre, char *in) { BiTNode *root; int j; if (*in == ‘\0 ’) return NULL ; for (j = 0 ; in[j] != *pre; ++j); in[j++] = ‘\0 ’; root = (BiTNode *)malloc (sizeof (BiTNode)); root->data = *pre; root->LChild = _Create(pre + 1 , in); root->RChild = _Create(pre + j, in + j); return root; }
树转二叉树 1 2 3 4 5 6 7 8 9 BiTree CS2Bi (CSTree rootCS) { if (rootCS == NULL ) return NULL ; BiTree rootBi = (BiTree)malloc (sizeof (BiTNode)); rootBi->data = rootCS->data; rootBi->LChild = CS2Bi(rootCS->FirstChild); rootBi->RChild = CS2Bi(rootCS->NextSibling); return rootBi; }
其他 树的删除 删除二叉树应该是后序删除
普通树的遍历 先序访问与二叉树先序相同;
后序访问与二叉树中序相同;
普通树无中序
图 基本概念 邻接矩阵 邻接表 算法思想 查找出度和入度 邻接矩阵 A的出度:A所在矩阵的行 中非∞值个数
A的入度:A所在矩阵的列 中非∞值个数
邻接表 A的出度:p=A->firstarc,p进行遍历,每遇到一个即i++,输出i
A的入度:双重循环,遍历全图(除了A向量的链表),遇到A即i++,输出i
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void FindID (AdjList *G, int indegree[]) { int i; ArcNode *p; for (i = 0 ; i < G->vexnum; ++i) indegree[i] = 0 ; for (i = 0 ; i < G->vexnum; ++i) { p = G->vertex[i].firstarc; while (p != NULL ) { ++indegree[p->adjvex]; p = p->nextarc; } } }
8-查找 线性查找 二分法 递归
1 2 3 if (bfind(1 ,m-1 ,key)!=-1 ) return ; bfind(m,h,key);
非递归
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int bfind (int a[], int len, int key) { int l, h, m; l = 0 ; h = len - 1 ; while (l <= h) { m = (l + h) / 2 ; if (key == a[m]) return m; else if (key < a[m]) h = m - 1 ; else l = m + 1 ; } return -1 ; }