第一章 绪论
1.1 数据结构的基本概念
1.1.1 数据结构的基本概念
数据对象
数据项相当于struct里面的变量,数据元素相当于struct类型,数据对象是struct的数组,数据是所有数据的集合
数据结构
1.1.2 数据结构的三要素
逻辑结构
数据结构的三要素
物理结构
顺序存储
链式存储
索引存储
散列存储
数据类型
1.2.1 算法的基本概念
算法的特性
有穷性
确定性
可行性
1.2.2 算法的时间复杂度
练习
1.2.3 算法的空间复杂度
程序运行时的内存需求
函数递归调用带来的内存开销
第二章 线性表
逻辑结构是指数据之间的关系。如树、图、表。
物理结构是指数据的存放方式。如链式、顺序、复合。
2.1 线性表的定义和基本操作
线性表的定义
线性表的基本操作
2.2 线性表的顺序表示
2.2.1 顺序表的定义
顺序表的定义
顺序表的实现——静态分配
函数内定义的数组是随机值,函数外的数组一定都是0(int main()函数),即局部变量随机,全局变量都是0(原因是内存上面是栈存放指令,下面是堆负责存放数据,堆是虚拟的,用一块开一块内存,最开始标记为全部指向零,但栈是开多少就是多少,里面的值不会清空
C++默认的栈空间是一M,不够大定义很大会报错,函数外面的变量会放在堆空间里面,没有长度限制只有内存限制
顺序表的实现——动态分配
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 #include <iostream> #include <cstring> #include <stdlib.h> using namespace std;const int inisize 10 ;const int N =100010 ;struct Selist { int *data; int maxsize; int length; }selist[N];void ini (Sqlist &L) { L.data=(int *)malloc (inisize *sizeof (int )) L.length=0 ; L.maxsize=inisize; }void addsize (Sqlist &L,int len) { int *p=L.data; L.data=(int *)malloc ((L.maxsize+len)*sizeof (int )); for (int i = 0 ;i<L.length;i++){ L.data[i]=p[i]; } L.maxsize=L.maxsize+len; free (p); }
由于p这个变量是一个局部域这个函数的变量,所以当这个函数执行结束以后,存储p这个变量的这些内存空间会被系统自动地回收
顺序表的实现
2.2.2.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 #include <iostream> #include <cstring> #include <stdlib.h> using namespace std;const int maxsize=10 ;const int inisize=10 ;const int N =100010 ;struct Sqlist { int data[maxsize]; int length; }sqlist[N];void insert (Sqlist &L,int i,int e) { for (int j = L.length;j>=i;j--) L.data[j]=L.data[j-1 ]; L.data[i-1 ]=e; L.length++; }bool insert (Sqlist &L,int i , int e) { if (i<1 ||i>L.length+1 ) return false ; for (int j =L.length;j>=i;j--) L.data[j]=L.data[j-1 ]; L.data[i-1 ]=e; L.length++; return true ; }int main () { ini (L); insert (L,3 ,3 ); return 0 ; }
插入操作的时间复杂度
顺序表的基本操作——删除
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 #include <iostream> #include <cstring> #include <stdlib.h> #include <cstdio.h> using namespace std;const int N =10010 ;const maxsize=10 ;struct Sqlist { int data[maxsize]; int length; }sqlist[N];void ini (Sqlist &L) { for (int i = 0 ;i<L.length;i++) L.data[i]=0 ; }bool delete (Sqlist &L,int i,int &e) { if (i<1 ||i>L.length) return false ; e=L.data[i-1 ]; for (int j = i;j<L.length;j++) L.data[j-1 ]=L.data[j]; L.length--; return true ; }int main () { ini (L); int e = -1 ; if (delete (L,3 ,e)) printf ("已删除第3个元素,删除元素值为%d\n" ,e); else printf ("位序i不合法发,删除失败\n" ); return 0 ; }
删除操作的时间复杂度
2.2.2.2 顺序表的查找
顺序表的按位查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> #include <cstring> #incldue <stdlib.h> #include <cstdio> using namespace std;const int N =10 ;struct Sqlist { int length; lnt data; }sqlist[N];void init (Sqlist &L) { for (int i=0 ;i<L.length;i++) L.data[i]=0 ; }int getnum (Sqlist L,int i ) { return L.data[i-1 ]; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> #include <cstring> #incldue <stdlib.h> using namespace std;const int N =10 ;struct Seqlist { int *data; int maxsize; int length; }seqlist[N];void init (Seqlist &L) { L.data=(int *)malloc (N*sizeof (int )); L.length =0 ; L.maxsize=N; }int getnum (Seqlist L,int i) { return L.data[i-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 #include <iostream> using namespace std;const int N =10 ;struct Seqlist { int *data; int maxsize; int length; }seqlist[N];void init (Seqlist &L) { L.data=(int *)malloc (N*sizeof (int )); L.length=0 ; L.maxsize=N; }int loc (Seqlist L,int e) { for (int i = 0 ;i<L.length;i++) if (L.data[i]==e) return i+1 ; return 0 ; }int binary (int l, int r,int x) { while (l<r){ int mid = l+r>>1 ; if (mid>=x) r = mid; else l = mid+1 ; } return l; }
结构类型的比较
1 2 3 4 5 6 7 struct Edge { int a,b,w; bool operator <(const Edge &W)const { return w<W.w; } }edges[M];
按值查找的时间复杂度
2.3 线性表的链式表示
2.3.1 单链表的定义
用代码定义一个单链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 struct Node { int val; Node* next; }*head;int main () { Node* p = new Node (); p->val = x; p->next = head; head = p; for (Node* p =head;p;p=p->next) cout<<p->val<<' ' ; cout<<endl; return 0 ; }
1 2 3 4 5 6 7 typedef struct Node { int data; struct Node * next; }Node,*head; Node* p=(Node* )malloc (sizeof (Node))
不带头结点的单链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 typedef struct Node { int data; struct Node *next; }Node,*head;bool init (head &L) { L = NULL ; return true ; }void test () { head L; init (L); }bool empty (head L) { if (L==NULL ) return true ; else return false ; }bool empty (head l) { return (L=NULL ); }
带头结点的单链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 struct Node { int val; Node* next; }*head;int main () { Node* p = new Node (); p->val = x; p->next = head; head = p; for (Node* p =head;p;p=p->next) cout<<p->val<<' ' ; cout<<endl; return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 typedef struct Node { int val; Node* next; }Node,*head;bool init (head &L) { L = (Node* )malloc (sizeof (Node)); if (L==NULL ) return false ; L->next = NULL ; return true ; }void test () { head L; init (L); }bool empty (head L) { if (L->next ==NULL ) return true ; else return false ; }
不带头结点 vs 带头结点
2.3.2.1 单链表的插入删除
关于简化图示的说明
按位序插入(带头结点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 struct Node { int val; Node* next; }*head;int main () { Node* p = new Node (); p->val = x; p->next = head; head = p; for (Node* p =head;p;p=p->next) cout<<p->val<<' ' ; cout<<endl; return 0 ; }
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 typedef struct Node { int val; Node* next; }Node,*head;bool init (head &L) { L = (Node* )malloc (sizeof (Node)); if (L==NULL ) return false ; L->next = NULL ; return true ; }void test () { head L; init (L); }bool insert (head &L,int i ,int e) { if (i<1 ) return false ; Node* p; int j = 0 ; p = L; while (p!=NULL && j<i-1 ){ p=p->next; j++; } if (p==NULL ) return false ; Node *s = (LNode *)malloc (sizeof (Node)); s->val=e; s->next=p->next; p->next = s; return true ; }
按位序插入(不带头结点)
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 typedef struct Node { int val; struct Node * next; }Node,*head;bool insert (head &L,int i ,int e) { if (i<1 ) return false ; if (i==1 ){ Node* s = (Node*)malloc (sizeof (Node)); s->data=e; s->next=L; L=s; return true ; } Node* p; int j = 1 ; p = L; while (p!=NULL &&j<i-1 ){ p = p->next; j++; } if (p=NULL ) return false ; Node* s= (Node*) malloc (sizeof (Node)); s->val = e; s->next = p->next; p->next = s; return true ; }
指定节点的后插操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 bool insertnext (Node *p,int e) { if (p==NULL ) return false ; Node* s=(Node *)malloc (sizeof (Node)); if (s==NULL ) return false ; s->data=e; s->next=p->next; p->next=s; return true ; }typedef struct Node { int data; struct Node * next; }Node,*head;
指定节点的前插操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 bool insertpri (Node* p,int e) { if (p==NULL ) return false ; Node *s = (Node*)malloc (sizeof (Node)); if (s==NULL ) return false ; s->next=p->next; p->next=s; s->data=p->data; p->data=e; return true ; }
按位序删除(带头结点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 typedef struct Node { int data; struct Node * next; }Node,*head;bool delete (head &L,int i ,int &e) { if (i<1 ) return false ; Node*p; int j = 0 ; p=L; while (p!=NULL &&j<i-1 ){ p=p->next; j++; } if (p==NULL ) return false ; if (p->next == NULL ) return false ; Node* q = p->next; e=q->data; p->next=q->next; free (q); return true ; }
指定节点的删除
1 2 3 4 5 6 7 8 9 10 bool remove (Node* p) { if (p==NULL ) return false ; Node* q=p->next; p->data=p->next->data p->next=q->next; free (q); return true ; }
封装的好处
2.3.2.2 单链表的查找
按位查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 typedef struct Node { int data; struct Node * next; }Node,*head;Node* getnum (head L,int i) { if (i<0 ) return NULL ; Node* p; int j = 0 ; p = L; while (p!=NULL && j<i){ p=p->next; j++; } return p; }
按值查找
1 2 3 4 5 6 7 8 9 10 11 12 typedef struct Node { int data; struct Node * next; }Node,*head;Node* find (head L,int e) { Node* p = L->next; while (p!=NULL &&p->data !=e) p=p->next; return p; }
1 2 3 4 5 6 7 8 9 10 11 struct Node { int val; Node*next; }*head;Node* find (int x) { for (Node *p = head;p;p->next) if (p->val==x) return p; }
求表的长度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct Node { int data; struct Node *next; }Node,*head;int length (head L) { int len =0 ; Node*p = L; while (p->next !=NULL ){ p=p->next; len++; } return len; }
1 2 3 4 5 6 7 8 9 10 11 struct Node { int val; Node *next; }*head;int getlength () { int len=0 ; for (Node *p = head;p;p->next) len++; return len; }
2.3.2.3 单链表的建立
尾插法建立单链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 typedef struct Node { int data; struct Node *next; }Node,*head;bool init (head &L) { L =(Node*) malloc (sizeof (Node)); if (L==NULL ) return false ; L->next = NULL ; return true ; }void test () { head L; init (L); }
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 bool insert (head &L,int i ,int e) { if (i<1 ) return false ; Node* p; int j = 0 ; p = L; while (p!=NULL && j<i-1 ){ p=p->next; j++; } if (p==NULL ) return false ; Node *s = (LNode *)malloc (sizeof (Node)); s->val=e; s->next=p->next; p->next = s; return true ; } 尾插法建立单链表: 初始化单链表 设置变量length记录链表长度 while 循环{ 每次取一个数据元素e insert (L,length+1 ,e) 插到尾部 ; length++; }
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 head listlist (list &L) { int x; L=(head)malloc (sizeof (Node)); Node*s,*r=L; cin>>x; while (x!=9999 ){ s=(Node*)malloc (sizeof (Node)); s->data=x; r->next =s; r=s; cin>>x; } r->next=NULL ; return L; }
头插法建立单链表
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 head listlist (head &L) { Node* s; int x; L=(head)malloc (sizeof (Node)); L->next=NULL ; cin>>x; while (x!=9999 ){ s=(Node* )malloc (sizeof (Node)); s->data=x; s->next=L->next; L->next=s; cin>>x; } return L; }
2.3.3 双链表
单链表 vs 双链表
双链表的初始化(带头结点)
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 typedef struct dnode { int data; struct dnode *prior,*next; }dnode,*dlist;bool init (dlist &L) { L=(dnode*)malloc (sizeof (dnode)); if (L==NULL ) return false ; L->prior=NULL ; L->next = NULL ; return true ; }void testdlist () { dlist L; init (L); }bool empty (dlist L) { if (L->next == NULL ) return true ; else return false ; }
双链表的插入
1 2 3 4 5 6 7 8 bool insert (dnode *p,denode *s) { s->next = p->next; p->next->prior=s; s->prior=p; p->next=s; }
1 2 3 4 5 6 7 8 9 10 11 bool insertnext (dnode* p,denode* s) { if (p==NULL ||s == NULL ) return false ; s->next=p->next; if (p->next !=NULL ) p->next->prior=s; s->prior=p; p->next=s; return true ; }
双链表的删除
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 bool deletenext (dnode *p) { if (p==NULL )return false ; dnode *q=p->next; if (q==NULL )return false ; p->next=q->next; if (q->next!=NULL ) q->next->prior=p; free (q); return true ; }void destroy (dlist &L) { while (L->next !=NULL ) deletenext (L); free (L); L=NULL ; }
双链表的遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 while (p!=NULL ){ p=p->next; }while (p!=NULL ){ p=p->prior; }while (p->prior!=NULL ){ p=p->prior; }
2.3.4 循环链表
循环单链表
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 typedef struct lnode {int data;struct lnode *next; }lnode,*head;bool init (head &L) { L=(lnode* )malloc (sizeof (lnode)); if (L==NULL ) return false ; L->next=L; return true ; }bool empty (head L) { if (L->next == L) return true ; else return false ; }bool istail (head L,lnode *p) { if (p->next==L) return true ; else return false ; }
循环双链表
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 typedef struct dnode { int data; struct dnode *prior,*next; }dnode,*head;bool init (head &L) { L=(dnode*)malloc (sizeof (dnode)); if (L==NULL ) return false ; L->prior=L; L->next =L; return true ; }void testdlist () { head L; init (L); }bool empty (head L) { if (L->next ==L) return true ; else return false ; }bool istail (head L,dnode*p) { if (p->next ==L) return true ; else return false ; }
双链表的插入
1 2 3 4 5 6 7 bool insert (dnode *p,dnode* s) { s->next = p->next; p->next->prior=s; s->prior=p; p->next=s; }
双链表的删除
1 2 3 4 p->next=q->next; q->next->prior=p;free (q);
2.3.5 静态链表
什么是静态链表
用代码定义一个静态链表
1 2 3 4 5 6 7 8 const int maxsize =10 ;struct Node { int data; int next; }node[maxsize];void test () { Node a; }
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 int head, e[N], ne[N], idx;void init () { head = -1 ; idx = 0 ; }void insert (int a) { e[idx] = a, ne[idx] = head, head = idx ++ ; }void add (int k ,int x) { e[idx] = x; ne[idx]=ne[k]; ne[k]=idx++; }void remove (int k) { ne[k]=ne[ne[k]]; }void remove () { head = ne[head]; }for (int i = head;i!=-1 ;i=ne[i]) cout<<e[i]<<endl;
简述基本操作的实现
1 2 3 4 5 #define maxsize 10 typedef struct { int data; int next; }slist[maxsize];
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 int head, e[N], ne[N], idx;void init () { head = -1 ; idx = 0 ; }void insert (int a) { e[idx] = a, ne[idx] = head, head = idx ++ ; }void add (int k ,int x) { e[idx] = x; ne[idx]=ne[k]; ne[k]=idx++; }void remove (int k) { ne[k]=ne[ne[k]]; }void remove () { head = ne[head]; }for (int i = head;i!=-1 ;i=ne[i]) cout<<e[i]<<endl;
2.3.6 顺序表和链表的比较
逻辑结构
存储结构
基本操作
用顺序表or链表
第三章 栈、队列和数组
3.1 栈
3.1.1 栈的基本概念
栈的定义
线性表的基本操作
栈的基本操作
栈的常考题型
3.1.2 栈的顺序存储实现
顺序栈的定义
1 2 3 4 5 #define maxsize 10 typedef struct { int data[maxsize] int top; }sqstack;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int stk[N], tt = 0 ; stk[ ++ tt] = x; tt -- ; stk[tt];if (tt > 0 ) not empty;else empty; stk[tt];
初始化操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #define maxsize 10 typedef struct { int data[maxsize] int top; }sqstack;void init (sqstack &s) { s.top=-1 ; }bool ifempty (sqstack s) { if (s.top==-1 ) return true ; else return false ; }
进栈操作
1 2 3 4 5 6 7 8 9 10 11 12 13 #define maxsize 10 typedef struct { int data[maxsize]; int top; }sqstack;bool push (sqstack &s,int x) { if (s.top==maxsize-1 ) return false ; s.top = s.top+1 ; s.data[s.top]=x; return true ; }
出栈操作
1 2 3 4 5 6 7 8 9 10 11 12 13 #define maxsize 10 typedef struct { int data[maxsize]; int top; }sqstack;bool pop (sqstack &s,int &x) { if (s.top==-1 ) return false ; x=s.data[s.top]; s.top=s.top-1 ; return true ; }
读取栈顶元素操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #define maxsize 10 typedef struct { int data[maxsize]; int top; }sqstack;bool pop (sqstack &s,int &x) { if (s.top==-1 ) return false ; x=s.data[s.top]; s.top=s.top-1 ; return true ; }bool gettop (sqstack s,int &x) { if (s.top==-1 ) return false ; x=s.data[s.top]; return true ; }
另一种方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #define maxsize 10 typedef struct { int data[maxsize]; int top; }sqstack;void init (sqstack &s) { s.top=0 ; }bool empty (sqstack s) { if (s.top==0 ) return true ; else return false ; }
共享栈
1 2 3 4 5 6 7 8 9 10 11 #define maxsize 10 typedef struct { int data[maxsize]; int top0; int top1; }shstack;void init (shstack &s) { s.top0=-1 ; s.top1=maxsize; }
3.1.3 栈的链式存储实现
链栈的定义
1 2 3 4 typedef struct linknode { int data; struct linknode *next; }*listack;
3.2 队列
3.2.1 队列的基本概念
队列的定义
队列的基本操作
3.2.2 队列的顺序实现
1 2 3 4 5 6 7 8 #define maxsize 10 typedef struct { int data[maxsize]; int front,rear; }sqqueue;void testqueue () { sqqueue Q; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int q[N], hh = 0 , tt = -1 ; q[ ++ tt] = x; hh ++ ; q[hh]; q[tt];if (hh <= tt) not empty; else empty;
初始化操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #define maxsize 10 typedef struct { int data[maxsize]; int front,rear; }sqqueue;void init (sqqueue &Q) { Q.rear=Q.front=0 ; }bool empty (sqqueue Q) { if (Q.rear==Q.front) return true ; else return false ; }void testqueue () { sqqueue Q; }
入队操作
1 2 3 4 5 6 7 8 9 10 11 12 13 #define maxsize 10 typedef struct { int data[maxsize]; int front,rear; }sqqueue;bool inq (sqqueue &Q,int x) { if (队列已满) return false ; Q.data[Q.rear]=x; Q.rear=Q.rear+1 ; return true ; }
循环队列
循环队列——入队操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 bool empty (sqq Q) { if (Q.rear==Q.front) return true ; else return false ; }bool inq (sqq &Q,int x) {if ((Q.rear+1 )%maxsize==Q.front)) return false ; Q.data[Q.rear]=x; Q.rear=(Q.rear+1 )%maxsize; return true ; }
循环队列——出队操作
![](/img/5 003054.jpg)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 bool dequeue (sqqueue &Q,int &x) { if (Q.rear==Q.front) return false ; x=Q.data[Q.front]; Q.front=(Q.front+1 )%maxsize; return true ; }bool gethead (sqqueue Q,int &x) { if (Q.rear==Q.front) return false ; x=Q.data[Q.front]; return true ; }
方案一:判断队列已满/已空
方案二:判断队列已满/已空
方案三:判断队列已满/已空
其他出题方法
3.2.3 队列的链式实现
1 2 3 4 5 6 7 8 typedef struct linknode { int data; struct linknode *next; }linknode;typedef struct { linknode *front,*rear; }linkqueue;
初始化(带头结点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 typedef struct linknode { int data; struct linknode *next; }linknode;typedef struct { linknode *front,*rear; }linkqueue;void init (linkqueue &Q) { Q.front=Q.rear=(linknode*)malloc (sizeof (linknode)); Q.front->next=NULL ; }void testlinkqueue () { linkqueue Q; init (Q); }bool empty (linkqueue Q) { if (Q.front==Q.rear) return true ; else return false ; }
初始化(不带头结点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void init (linkqueue &Q) { Q.front=NULL ; Q.rear=NULL ; }bool empty (linkqueue Q) { if (Q.front ==NULL ) return true ; else return false ; }
入队(带头结点)
1 2 3 4 5 6 7 8 void enqueue (linkqueue &Q,int x) { linknode *s=(linknode *)malloc (sizeof (linknode)); s->data=x; s->next=NULL ; Q.rear->next=s; Q.rear=s; }
入队(不带头结点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void enqueue (linkqueue &Q,int x) { linknode *s=(linknode *)malloc (sizeof (linknode)); s->data=x; s->next=NULL ; if (Q.front==NULL ){ Q.front=s; Q.rear=s; } else { Q.rear->=x; Q.rear=s; } }
出队(带头结点)
1 2 3 4 5 6 7 8 9 10 11 12 bool dequeue (linkqueue &Q,int &x) { if (Q.front==Q.rear) return false ; linknode *p=Q.front->next; x=p->data; Q.front->next=p->next; if (Q.rear==p) Q.rear=Q.front; free (p); return true ; }
出队(不带头结点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 bool dequeue (linkqueue &Q,int &x) { if (Q.front==NULL ) return false ; linknode *p=Q.front; x=p->data; Q.front=p->next; if (Q.rear==p){ Q.front=NULL ; Q.rear=NULL } free (p); return true ; }
队列满的条件
3.2.4 双端队列
双端队列
考点:判断输出序列合法性
若果在输出序列中看到某一个序号的元素,那么在这个元素输出之前,意味着它之前的所有的那些元素,肯定都已经输入到这个队列里边了
栈
输入受限的双端队列
下划线是栈中非法但是输入受限的双端队列中合法的输出序列
输出受限的双端队列
3.3 栈和队列的应用
3.3.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 #include <iostream> #include <cstring> #include <stack> #include <vector> #include <unordered_map> #include <algorithm> using namespace std; stack<int > num; stack<char > op;void eval () { auto b = num.top ();num.pop (); auto a = num.top ();num.pop (); auto c = op.top ();op.pop (); int x ; if (c=='-' ) x = a-b; else if (c=='+' )x=a+b; else if (c=='*' ) x =a*b; else x = a/b; num.push (x); }int main () { string str; cin>>str; unordered_map<char ,int > pr{{'+' ,1 },{'-' ,1 },{'*' ,2 },{'/' ,2 }}; for (int i = 0 ;i<str.size ();i++){ auto c = str[i]; if (isdigit (c)){ int x = 0 ,j=i; while (j<str.size ()&&isdigit (str[j]))x=x*10 +str[j++]-'0' ; num.push (x); i=j-1 ; } else if (c=='(' ) op.push (c); else if (c==')' ){ while (op.size ()&&op.top ()!='(' ) eval (); op.pop (); } else { while (op.size ()&&op.top ()!='(' &&pr[op.top ()]>=pr[c])eval (); op.push (c); } } while (op.size ())eval (); cout<<num.top (); return 0 ; }
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 #define maxsize 10 typedef struct { char data[maxsize]; int top; }sqstack;void init (sqstack &S) bool empty (sqstack S) bool push (sqstack &S,char x) bool pop (sqstack &S,char &x) bool check (char str[],int length) { sqstack S; init (S); for (int i = 0 ;i<length;i++){ if (str[i]=='(' ||str[i]=='[' ||str[i]=='{' ){ push (S,str[i]); } else { if (empty (S)) return false ; char top; pop (S,top); if (str[i]==')' &&top!='(' ) return false ; if (str[i]==']' &&top!='[' ) return false ; if (str[i]=='}' &&top!='{' ) return false ; } } return empty (S); }
3.3.2 栈在表达式求值中的应用
熟悉的算术表达式
中缀、后缀、前缀表达式
中缀表达式转后缀表达式(手算)
后缀表达式中的运算符从左到右的先后顺序和中缀表达式当中运算符的生效次序是相同的
中缀表达式转后缀表达式(机算)
带括号的情况
当栈顶是左括号的时候,不需要弹出任何元素,直接把当前的运算符压入栈
![](/img/254545 .gif)
后缀表达式的计算(手算)
后缀表达式的计算(机算)
中缀表达式适合人类运算,后缀表达式适合机器进行运算,因为不用判断运算符的优先级,从左到右直接运算即可
中缀表达式转前缀表达式(手算)
按照右优先规则确定的这些运算符的生效顺序,和我们前缀表达式当中各个运算符从右到左出现的次序是相同的
前缀表达式的计算
先出栈的是左操作数,后出栈的是右操作数
和后缀表达式的运算刚好相反
后缀表达式中,先出栈的应该是右操作数,后出栈的是左操作数
中缀表达式的计算(用栈实现)
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 #include <iostream> #include <cstring> #include <stack> #include <vector> #include <unordered_map> #include <algorithm> using namespace std; stack<int > num; stack<char > op;void eval () { auto b = num.top ();num.pop (); auto a = num.top ();num.pop (); auto c = op.top ();op.pop (); int x ; if (c=='-' ) x = a-b; else if (c=='+' )x=a+b; else if (c=='*' ) x =a*b; else x = a/b; num.push (x); }int main () { string str; cin>>str; unordered_map<char ,int > pr{{'+' ,1 },{'-' ,1 },{'*' ,2 },{'/' ,2 }}; for (int i = 0 ;i<str.size ();i++){ auto c = str[i]; if (isdigit (c)){ int x = 0 ,j=i; while (j<str.size ()&&isdigit (str[j]))x=x*10 +str[j++]-'0' ; num.push (x); i=j-1 ; } else if (c=='(' ) op.push (c); else if (c==')' ){ while (op.size ()&&op.top ()!='(' ) eval (); op.pop (); } else { while (op.size ()&&op.top ()!='(' &&pr[op.top ()]>=pr[c])eval (); op.push (c); } } while (op.size ())eval (); cout<<num.top (); return 0 ; }
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 #include <iostream> #include <cstring> #include <algorithm> #include <stack> #include <unordered_map> using namespace std; stack<int > num; stack<char > op;void eval () { auto b = num.top (); num.pop (); auto a = num.top (); num.pop (); auto c = op.top (); op.pop (); int x; if (c == '+' ) x = a + b; else if (c == '-' ) x = a - b; else if (c == '*' ) x = a * b; else x = a / b; num.push (x); }int main () { unordered_map<char , int > pr{{'+' , 1 }, {'-' , 1 }, {'*' , 2 }, {'/' , 2 }}; string str; cin >> str; for (int i = 0 ; i < str.size (); i ++ ) { auto c = str[i]; if (isdigit (c)) { int x = 0 , j = i; while (j < str.size () && isdigit (str[j])) x = x * 10 + str[j ++ ] - '0' ; i = j - 1 ; num.push (x); } else if (c == '(' ) op.push (c); else if (c == ')' ) { while (op.top () != '(' ) eval (); op.pop (); } else { while (op.size () && op.top () != '(' && pr[op.top ()] >= pr[c]) eval (); op.push (c); } } while (op.size ()) eval (); cout << num.top () << endl; return 0 ; }
![](/img/233333 00_00_17-00_02_02Part001 00_00_00-00_00_30.gif)
![](/img/233333 00_00_17-00_02_02Part002 00_00_00-00_00_30.gif)
![](/img/233333 00_00_17-00_02_02Part003 00_00_00-00_00_30.gif)
![](/img/233333 00_00_17-00_02_02Part004 00_00_00-00_00_30.gif)
3.3.3 栈在递归中的应用
函数调用背后的过程
在函数里边修改a的值或者b的值,那么它修改的其实是函数中a和b的值,但是main函数里面的a、b这两个变量其实对应的是内存当中的这两份数据,因此在func1里面修改a和b的值,影响不到main函数里面a和b的值
函数执行完之后把它的相关信息弹出栈,释放对应的内存空间
栈在递归中的应用
3.3.4 队列的应用
队列应用——树的层次遍历
首先被遍历的是根节点,遍历到1号节点的时候把1号节点的左右孩子塞入队列的队尾
遍历完1号节点之后,就可以出队,从队列中删除1号节点,接下来检查新队头节点即2号节点,同样的把2号节点的左右孩子放入队尾
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 queue<int > q; st[1 ] = true ; q.push (1 );while (q.size ()) { int t = q.front (); q.pop (); for (int i = h[t]; i != -1 ; i = ne[i]) { int j = e[i]; if (!st[j]) { st[j] = true ; q.push (j); } } }
队列应用——图的广度优先遍历
遍历1号节点,检查1号节点相邻的其他节点有没有被遍历过,没被遍历过放到队列的队尾,处理完1号结点之后就可以让他,即1号节点出队
接下来处理2号节点,看2号相邻的节点中有没有还没被处理过的节点,把没有处理过的放到队列末尾,然后2号可以出队
队列在操作系统中的应用
3.4 数组和特殊矩阵
一维数组的存储结构
二维数组的存储结构
普通矩阵的存储
对称矩阵的压缩存储
万变不离其宗,具体转换只需要看aij对应的是数组中的哪一个元素即可
列优先
前面有j-1列,i-j就是该位置前面有多少个元素(按列数),最后加上这个数字本身
三角矩阵的压缩存储
按照行优先或者列优先的原则把不是常量的数据都放到一个一维数组里面
三对角矩阵的压缩存储
稀疏矩阵的压缩存储
顺序存储
十字链表法
绿色数组对应系数矩阵中的各行,橙色数组对应稀疏矩阵的各列,每个非零元素对应一个节点
第四章 串
4.1 串的定义和实现
4.1.1 串的定义和基本操作
串的定义
串vs线性表
串的基本操作
字符集编码(ASCII码)
拓展:乱码问题
4.1.2 串的存储结构
串的顺序存储
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #define maxlen 255 typedef struct { char ch[maxlen]; int length; }sstring;typedef strct{ char *ch; int length; }hstring; hstring s; s.ch = (char *)malloc (maxlen * sizeof (char )); s.length =0 ;
方案三的缺点:查询字符串长度的时间复杂度为O(n)
方案二的缺点:字符串长度的范围是8bit即255,比较短
方便随机存储,但增删改查不方便
串的链式存储
1 2 3 4 5 6 7 8 9 typedef struct stringnode { char ch; struct stringnode *next; }stringnode, *string;typedef struct stringnode { char ch[4 ]; struct stringnode *next; }stringnode, *string;
基本操作的实现
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 #typedef maxlen 255 typedef struct { char ch[maxlen]; int length; }sstring;bool substring (sstring &sub,sstring s,int pos,int len) { if (pos+len-1 >s.length) return false ; for (int i = pos;i<pos+len;i++) sub.ch[i-pos+1 ]=s.ch[i]; sub.length =len; return true ; }int strcompare (sstring s,sstring t) { for (int i = 1 ;i<=s.length && i<=t.length;i++){ if (s.ch[i]!=t.ch[i]) return s.ch[i]-t.ch[i]; } return s.length-t.length; }int index (sstring s,sstring t) { itn i=1 ,n=strlength (s),m=strlength (t); sstring sub; while (i<=n-m+1 ){ substring (sub,s,i,m); if (strcompare (sub,t)!=0 )++i; else return i; } return 0 ; }
4.2 串的模式匹配
4.2.1 朴素模式匹配算法
是什么
朴素模式匹配算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int index (sstring s,sstring t) { int i = 1 ,j=1 ; while (i<=s.length &&j<=t.length){ if (s.ch[i]==t.ch[j]) i++,j++; else i=i-j+2 ,j=1 ; } if (j>t.length) return i-t.length; else return 0 ; }
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 s[N],p[M];for (int i = 1 ;i<=n;i++){bool flag = true ;for (int j = 1 ;j<=m;j++)if (s[i+j-1 ]!=p[j]){ flag = false ;break ; } }#include <iostream> #include <cstring> #include <stdio.h> using namespace std;int main () { ios::sync_with_stdio (false ); cin.tie (0 ); string a; string b; getline (cin,a); getline (cin,b); bool flag=true ; int N,M; N=a.length (); M=b.length (); for (int i = 0 ;i<N;i++){ flag = true ; for (int j = 0 ;j<M;j++){ if (a[i+j-1 ]!=b[j]){ flag = false ; break ; } } if (flag) { cout<<i<<endl; break ; } } if (!flag)cout<<"false" <<endl; system ("pause" );return 0 ; }
4.2.2 KMP算法
yxc笔记
存在五个相等的轴,其中:
由于第一段第二段在i-j都相等,所以P①=S①,P②=S②
由于J+1匹配失败,P字串向后移动直到再次匹配,此时P③=S②,由于S②=P②,所以P②=P③
由于P实际上为字串位移,所以P③=P①
综上,存在五段相等的轴
因此,只需要求子串P中,P①=P②的最大区域,当该区域越大, P再次匹配往后移动的距离越短
因此,对于字串P:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 求模式串的Next数组:for (int i = 2 , j = 0 ; i <= m; i ++ ) { while (j && p[i] != p[j + 1 ]) j = ne[j]; if (p[i] == p[j + 1 ]) j ++ ; ne[i] = j; }for (int i = 1 , j = 0 ; i <= n; i ++ ) { while (j && s[i] != p[j + 1 ]) j = ne[j]; if (s[i] == p[j + 1 ]) j ++ ; if (j == m) { j = ne[j]; } }
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 #include <iostream> using namespace std;const int N =10010 ,M=100010 ;int n ,m;char p[N],s[M];int ne[N];int main () { ios::sync_with_stdio (0 ); cin.tie (0 ); cin >> n>>p+1 >>m>>s+1 ; for (int i = 2 ,j=0 ;i<=n;i++){ while (j&&p[i]!=p[j+1 ]) j = ne[j]; if (p[i]==p[j+1 ])j++; ne[i] = j; } for (int i = 1 ,j=0 ;i<=m;i++) { while (j&&s[i]!=p[j+1 ]) j = ne[j]; if (s[i]==p[j+1 ]) j++; if (j==n) { cout<<i-n<<" " ; j=ne[j]; } } }
kmp匹配过程
求next数组的过程
完整过程
朴素模式匹配算法优化思路
找到前缀和后缀相同的子串,然后回溯到前缀的下一个位置
next指明了当我们在模板串的第几个元素适配的时候,应该把j的值修改为多少
KMP算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int kmp (sstring s,sstring t,int nex[]) { int i = 1 ,j=1 ; while (i<=s.length&&j<=t.length){ if (j==0 ||s.ch[i]==t.ch[j]){ ++i; ++j; } else j=next[j]; } if (j>t.length) return i-t.length; else return 0 ; }
朴素模式匹配vsKMP算法
求模板串的next数组(手算练习)
next数组的优化
练习:求nextval数组
1 2 3 4 5 6 7 8 nextval[1 ]=0 ;for (int j = 2 ;j<=t.length;j++){ if (t.ch[next[j]]==t.ch[j]) nextval[j]=nextval[next[j]]; else nextval[j]=next[j]; }
next和nextval数组的个人总结
next数组采用求匹配失败的元素的左边起子串的前缀后缀相同的下一个位置
nextval数组j=1的位置无脑写0,然后后面的进行回溯,如果回溯到不一样的就保持不变,一样的就继续回溯
第五章 树与二叉树
5.1 树的基本概念
5.1.1 树的定义和基本术语
树的基本概念
结点、树的属性描述
树vs森林
5.1.2 树的性质
树的常考性质
5.2 二叉树的概念
5.2.1 二叉树的定义和基本术语
二叉树的基本概念
二叉树的五种状态
几个特殊的二叉树
满二叉树
除了最下面的叶子节点之外,其他的所有的分支节点都长满了两个分支
子结点数要么为2要么为0,不存在度为1的结点
这里的取整都是向下取整
二叉排序树
平衡二叉树
5.2.2 二叉树的性质
二叉树的常考性质
5.2.3 二叉树的存储结构
二叉树的顺序存储
1 2 3 4 5 6 7 8 9 #define maxsize 100 struct treenode { int value; bool isempty; }; treenode t[maxsize];for (int i = 0 ;i<maxsize;i++) t[i].isempty = true ;
完全二叉树的情况
非完全二叉树的情况
对于是否有左右孩子的判断只能用isempty来进行判断
存在大量空置单元
二叉树的链式存储
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 typedef struct bitnode { int data; struc bitnode *lkid,*rkid; }bitnode,*bitree; bitree root =NULL ; root = (bitree)malloc (sizeof (bitnode)); root->data={1 }; root->lchild =NULL ; root->rchild=NULL ; bitnode *p=(bitnode *)malloc (sizeof (bitnode)); p->data={2 }; p->lchild =NULL ; p->rchild =NULL ; root ->lchild =p;
1 2 3 4 5 typedef struct trinode { int data; struct trinode *lchild,*rchild,*parent }trinode,*tritree;
5.3 二叉树的遍历和线索二叉树
5.3.1 二叉树的先中后序遍历
二叉树的遍历
先序遍历(代码)
1 2 3 4 5 6 7 8 9 10 11 12 13 typedef struct binode { int data; struct binode *lkid,*rkid; }binode,*bitree;void pre (bitree T) { it (T!=NULL ){ visit (T); pre (T->lkid); pre (T->rkid); } }
中序遍历(代码)
1 2 3 4 5 6 7 8 9 10 11 12 13 typedef struct binode { int data; struct binode *lkid,*rkid; }binode,*bitree;void inorder (bitree t) { if (t!=NULL ){ inorder (t->lkid); visit (T); inorder (t->rkid); } }
后序遍历(代码)
1 2 3 4 5 6 7 8 9 10 11 12 typedef struct binode { int data; struct binode *lkid,*rkid; }binode,*bitree;void postorder (bitree t) { if (t!=NULL ){ postorder (t->lkid); postorder (t->rkid); visit (t); } }
求先序遍历序列
求中序遍历序列
求后序遍历序列
例:求树的深度(应用)
1 2 3 4 5 6 7 8 9 10 11 12 13 int dep (bitree t) {if (t==NULL ){ return 0 ; }else {int l = dep (t->lkid);int r = dep (t->rkid); return l>r?l+1 :r+1 ; } }
5.3.2 二叉树的层次遍历
二叉树的层序遍历
代码实现
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 typedef struct node { char data; struct node *lkid,*rkid; }node,*tree;typedef struc lnode{ node *data; struct lnode *next; }lnode;typedef struct { lnode *front,*rear; }queue;void fs (tree t) { queue Q; init (Q); tree p; enqueue (Q,T); while (!isempty (Q)){ dequeue (Q,p); visit (p); if (p->lkid!=NULL ) enqueue (Q,p->lkid); if (p->rkid!=NULL ) enqueue (Q,p->rkid); } }
5.3.3 由遍历序列构造二叉树
由遍历序列构造二叉树
前序+中序遍历序列
例一
例二
后序+中序遍历序列
例
层序+中序遍历序列
例
例2
5.3.4 线索二叉树的概念
中序线索二叉树
线索二叉树的存储结构
1 2 3 4 5 6 typedef struct node { int data; struct node *lkid,*rkid; int ltag,rtag; }node,*tree;
先序线索二叉树
后序线索二叉树
三种线索二叉树的对比
5.3.5 二叉树的线索化
用土办法找到中序前驱
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void fs (tree t) { if (t!=NULL ){ fs (t->lkid); visit (t); fs (t->rkid); } }void visit (tree *q) { if (q==p) final = pre; else pre=q; } node *p; node *pre =NULL ; node *final =NULL ;
中序线索化
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 typedef struct node { int data; struct node *l,*r; int lt,rt; }node ,*tree; node *pre=NULL ;void tread (tree t) { if (t!=NULL ){ tread (t->l); visit (t); tread(t->r); } }void visit (node *q) { if (q->l==NULL ){ q->l=pre; q->lt=1 ; } if (pre!=NULL &&pre->r==NULL ){ pre->r=q; pre->rt=1 ; } pre=q; }void creat (tree t) { pre =NULL ; if (t!=NULL ){ tread (t); if (pre->r==NULL ) pre->rt=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 node *pre=NULL ;void prethread (tree t) { if (t!=NULL ){ visit (t); if (t->lt==0 ) pre (t->l); pre (t->r); } }void creat (tree t) { pre =NULL ; if (t!=NULL ){ prethread (t); if (pre->r==NULL ) pre->rt=1 ; } }void visit (node *q) { if (q->l==NULL ){ q->l=pre; q->lt=1 ; } if (pre!=NULL &&pre->r==NULL ){ pre->r=q; pre->rt=1 ; } pre=q; }
后续线索化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 node *pre=NULL;void post (tree t ) { if (t!=NULL){ post (t->l); post (t->r); visit(t); } }void creat (tree t ) { pre=NULL; if (t!=NULL){ post(t); if (pre->r==NULL) pre->rt=1 ; } }
5.3.6 在线索二叉树中找前驱后继
中序线索二叉树找中序后继
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 node *first (node *p) { while (p->lt==0 )p=p->l; return p; }node *nextnode (node *p) { if (p->rt==0 )return first (p->r); else return p->r; }void inorder (node*t) { for (node *p=first (t);p!=NULL ;p=nextnode (p)) visit (p); }
中序线索二叉树找中序前驱
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 node *last (node*p) { while (p->rt==0 )p=p->r; return p; }node *prenode (node *p) { if (p->lt==0 )return lastnode (p->l); else return p->l; }void rev (node *t) { for (node *p=last (t);p!=NULL ;p=prenode (p)) visit (p); }
先序线索二叉树找先序后继
后续线索二叉树找后续前驱
后续线索二叉树找后续后继
5.4 树、森林
5.4.1 树的存储结构
树的逻辑结构
双亲表示法(顺序存储)
1 2 3 4 5 6 7 8 9 10 #define maxsize 100 typedef struct { int data; int parent; }ptnode;typedef struct { ptnode nodes[maxsize]; int n; }ptree;
增
删
查
孩子表示法(顺序+链式存储)
1 2 3 4 5 6 7 8 9 10 11 12 struct ctnode { int kid; struct ctnode *next; };typedef struct { int data; struct ctnode *first; }ctbox;typedef struct { ctbox nodes[maxsize]; int n,r; }ctree;
孩子兄弟表示法(链式存储)
1 2 3 4 5 typedef struct csnode { int data; struct csnode *first,*next; }csnode,*cstree;
森林和二叉树的转换
5.4.2 树和森林的遍历
树的先根遍历
1 2 3 4 5 6 7 8 void preorder (node *r) { if (r!=NULL ){ visit (r); while (r还有下一个子树t) preorder (t); } }
树的后根遍历
1 2 3 4 5 6 7 8 9 void postorder (node *r) { if (r!=NULL ){ while (r还有下一个子树t) postorder (t); visit (r); } }
树的层次遍历(树的宽度遍历)
森林的先序遍历
森林的中序遍历
深度优先遍历yxc
1 2 3 4 5 6 7 8 9 10 11 bool st[N];int dfs (int u) { st[u] = true ; for (int i = h[u]; i != -1 ; i = ne[i]) { int j = e[i]; if (!st[j]) dfs (j); } }
宽度优先遍历yxc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 queue<int > q; st[1 ] = true ; q.push (1 );while (q.size ()) { int t = q.front (); q.pop (); for (int i = h[t]; i != -1 ; i = ne[i]) { int j = e[i]; if (!st[j]) { st[j] = true ; q.push (j); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int bfs () { int hh =0 ,tt=0 ; q[0 ]=1 ; memset (d,-1 ,sizeof d); d[1 ]=0 ; while (hh<=tt){ int t=q[hh++] for (int i = h[t];i!=-1 ;i=e[i]) { int j =e[i]; if (d[j]==-1 ) { d[j]=d[t]+1 ; q[++tt]=j; } } } return 0 ; }
5.5 树与二叉树的应用
5.5.1 哈夫曼树
![](/img/0 223838.jpg)
带权路径长度
哈夫曼树的定义
哈夫曼树的构造
每一次从节点集合当中选择两个权值最小的结点,成为左右节点,再让这两个点的权值之和成为新树的根节点的权值
之后选择权值最小的根节点作为左右节点组成新的树,再由这两个结点的权值和作为新的根节点的权值
哈夫曼编码
英文字母频次
5.5.2 并查集
并查集yxc笔记
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 (1 )朴素并查集: int p[N]; int find (int x) { if (p[x] != x) p[x] = find (p[x]); return p[x]; } for (int i = 1 ; i <= n; i ++ ) p[i] = i; p[find (a)] = find (b); if (find (a)==find (b)) puts ("yes" ); else puts ("no" ); (2 )维护size的并查集: int p[N], size[N]; int find (int x) { if (p[x] != x) p[x] = find (p[x]); return p[x]; } for (int i = 1 ; i <= n; i ++ ) { p[i] = i; size[i] = 1 ; } if (find (a)==find (b))continue ; size[find (b)] += size[find (a)]; p[find (a)] = find (b); cout<<size[find (a)]; (3 )维护到祖宗节点距离的并查集: int p[N], d[N]; int find (int x) { if (p[x] != x) { int u = find (p[x]); d[x] += d[p[x]]; p[x] = u; } return p[x]; } for (int i = 1 ; i <= n; i ++ ) { p[i] = i; d[i] = 0 ; } p[find (a)] = find (b); d[find (a)] = distance;
逻辑结构——集合
并查集的存储结构
并查集的基本操作
并查集的代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #define size 13; int p[size];void init (int s[]) { for (int i = 0 ;i<size;i++) s[i]=-1 ; }int find (int s[],int x) { while (s[x]>=0 ) x=s[x]; return x; }void union (int s[],int root1,int root2) { if (root1==root2)return ; s[root2]=root1; }
时间复杂度分析
并操作的优化
1 2 3 4 5 6 7 8 9 10 11 12 13 void union (int s[],int root1,int root2) { if (root1==root2)return ; if (s[root2]>s[root1]){ s[root1]+=s[root2]; s[root2]=root1; } else { s[root2]+=s[root1]; s[root1]=root2; } }
5.5.3 并查集的进一步优化
拓展:find操作的优化(压缩路径)
1 2 3 4 5 6 7 8 9 10 11 12 13 int find (int s[],int x) { int root =x; while (s[root]>=0 ) root=s[root]; while (x!=root){ int t=s[x]; s[x]=root; x=t; } return root; }
并查集的优化
第六章 图
6.1 图的基本概念
6.1.1 图的基本概念
图的定义
无向图、有向图
简单图、多重图
顶点-顶点的关系描述
连通图、强连通图
研究图的局部——子图
连通分量
强连通分量
生成树
生成森林
边的权、带权图/网
几种特殊形态的图
![](/img/23 233420.jpg)
6.2 图的存储及基本操作
6.2.1邻接矩阵法
图的存储——邻接矩阵法
无向图中1表示两点之间有一条边
无向图中1表示有一条从A指向B的边
1 2 3 4 5 6 7 #define max 100 typedef struct { char vex[max]; int edge[max][max] int vexnum,arcnum; }mgraph;
邻接矩阵法存储带权图(网)
1 2 3 4 5 6 7 8 9 #define max 100 #define INF=0x3f3f3f3f typedef char vertextype;typedef int edgetype;typedef struct { vertextype vex[max]; edgetype edge[max][max]; int vexnum,arcnum; }mgraph;
邻接矩阵法的性能分析
6.2.2 邻接表法
邻接表法(顺序+链式存储)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int h[N], e[2 N], ne[2 N], idx;void add (int a, int b) { e[idx] = b; ne[idx] = h[a]; h[a] = idx ++ ; } idx = 0 ;memset (h, -1 , sizeof h);for (int i = 0 ;i<m;i++) { int a,b; cin>> a >>b; add (a,b); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 typedef struct arcnode { int adjvex; struct arcnode *next; }typedef struct node { vertextype data; arcnode *first; }node,adjlist[max];typedef struct { adjlist vertices; int vexnum,arcnum; }algraph;
6.2.3 十字链表、邻接多重表
十字链表存储有向图
沿着绿色一直往后找各个节点的话,可以找到从当前这个顶点往外发射的边
沿着橙色一直往后找各个结点的话,可以找到所有指向当前顶点的弧
十字链表法性能分析
邻接矩阵、邻接表存储无向图
邻接多重表存储无向图
6.2.4 图的基本操作
图的基本操作
判断是否存在边
列出与某个节点邻接的边
具体问题具体分析,注意稠密图和稀疏图中V和E的区别
插入顶点
删除顶点x
增加一条边
找到指定顶点的第一个邻接点
找到指定顶点的第一个邻接点接下来的后一个邻接点
设置/获取某条边的权值
6.3 图的遍历
6.3.1 图的广度优先遍历
树vs图
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 bool visit[max];void bfstraverse (graph g) { for (i=0 ;i<g.vexnum;i++) visited[i]=false ; init (Q); for (i=0 ;i<g.vexnum;i++) if (!visited[i]) BFS (G,i); }void bfs (graph g,int v) { visit (v) visited[v]=true ; enqueue (q,v); while (!isempty (q)){ dequeue (q,v); for (w=firstneighbor (g,v);w>=0 ;w=nextneighbor (g,v,w)) if (!visited[w]){ visit (w); visited[w]true ; enqueue (q,w); } } }
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 queue<int > q; st[1 ] = true ; q.push (1 );while (q.size ()) { int t = q.front (); q.pop (); for (int i = h[t]; i != -1 ; i = ne[i]) { int j = e[i]; if (!st[j]) { st[j] = true ; q.push (j); } } }int bfs () { int hh =0 ,tt=0 ; q[0 ]=1 ; memset (d,-1 ,sizeof d); d[1 ]=0 ; while (hh<=tt){ int t=q[hh++] for (int i = h[t];i!=-1 ;i=e[i]) { int j =e[i]; if (d[j]==-1 ) { d[j]=d[t]+1 ; q[++tt]=j; } } } return 0 ; }
广度优先遍历序列
遍历序列的可变性
算法存在的问题
BFS算法(final版)
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 bool visit[max];void bfstraverse (graph g) { for (i=0 ;i<g.vexnum;i++) visited[i]=false ; init (Q); for (i=0 ;i<g.vexnum;i++) if (!visited[i]) BFS (G,i); }void bfs (graph g,int v) { visit (v) visited[v]=true ; enqueue (q,v); while (!isempty (q)){ dequeue (q,v); for (w=firstneighbor (g,v);w>=0 ;w=nextneighbor (g,v,w)) if (!visited[w]){ visit (w); visited[w]true ; enqueue (q,w); } } }
复杂度分析
广度优先生成树
广度优先生成森林
有向图的BFS过程
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 bool visit[max];void bfstraverse (graph g) { for (i=0 ;i<g.vexnum;i++) visited[i]=false ; init (Q); for (i=0 ;i<g.vexnum;i++) if (!visited[i]) BFS (G,i); }void bfs (graph g,int v) { visit (v) visited[v]=true ; enqueue (q,v); while (!isempty (q)){ dequeue (q,v); for (w=firstneighbor (g,v);w>=0 ;w=nextneighbor (g,v,w)) if (!visited[w]){ visit (w); visited[w]true ; enqueue (q,w); } } }
6.3.2 图的深度优先遍历
图的优先遍历
1 2 3 4 5 6 7 8 9 10 11 12 bool st[N];int dfs (int u) { st[u] = true ; for (int i = h[u]; i != -1 ; i = ne[i]) { int j = e[i]; if (!st[j]) dfs (j); } }
1 2 3 4 5 6 7 8 9 10 11 bool visit[max];void dfs (graph g,int v) { visit (v); visited[v]=true ; for (w=firstneighbor (g,v);w>=0 ;w=nextneighbor (g,v,w)) if (!visited[w]){ dfs (g,w); } }
算法存在的问题
dfs算法(final版)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 bool visit[max];void dfstravers (graph g) { for (v=0 ;v<g.vexnum;++v) visited[v]=false ; for (v=0 ;v<g.vexnum;++v) if (!visited[v]) dfs (g,v); }void dfs (graph g,int v) { visit (v); visited[v]=true ; for (w=firstneighbor (g,v);w>=0 ;w=nextneighbor (g,v,w)) if (!visited[w]){ dfs (g,w); } }
复杂度分析
深度优先遍历序列
深度优先生成树
深度优先生成森林
图的遍历与图的连通性
6.4 图的应用
6.4.1 最小生成树
最小生成树(最小代价树)
初始阶段考查prim算法和kruskal算法代码的概率不高
prim算法
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 int n; int g[N][N]; int dist[N]; bool st[N]; int prim () { memset (dist, 0x3f , sizeof dist); int res = 0 ; for (int i = 0 ; i < n; i ++ ) { int t = -1 ; for (int j = 1 ; j <= n; j ++ ) if (!st[j] && (t == -1 || dist[t] > dist[j])) t = j; if (i && dist[t] == INF) return INF; if (i) res += dist[t]; st[t] = true ; for (int j = 1 ; j <= n; j ++ ) dist[j] = min (dist[j], g[t][j]); } return res; }
kruskal算法
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 int n, m; int p[N]; struct Edge { int a, b, w; bool operator < (const Edge &W)const { return w < W.w; } }edges[M];int find (int x) { if (p[x] != x) p[x] = find (p[x]); return p[x]; }int kruskal () { sort (edges, edges + m); for (int i = 1 ; i <= n; i ++ ) p[i] = i; int res = 0 , cnt = 0 ; for (int i = 0 ; i < m; i ++ ) { int a = edges[i].a, b = edges[i].b, w = edges[i].w; a = find (a), b = find (b); if (a != b) { p[a] = b; res += w; cnt ++ ; } } if (cnt < n - 1 ) return INF; return res; }
prim算法 vs kruskal算法
prim算法的实现思想
kruskal算法的实现思想
6.4.2 最短路径问题(bfs算法)
最短路径问题
单源最短路径是指从一个源头出发到达其他任意一个顶点
bfs求无权图的单源最短路径
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void bfs (graph g,int u) { for (i=0 ;i<g.vexnum;i++){ d[i]=INF; path[i]=-1 ; } d[u]=0 ; visited[u]=true ; enqueue (q,u); while (!isempty (q)){ dequeue (q,u); for (w=firstneighbor (g,u);w>=0 ;w=nextneighbor (g,u,w)) if (!visited[w]){ d[w]=d[u]+1 ; path[w]=u; visited[w]=true ; enqueue (q,w); } } }
6.4.3 最短路径问题(dijkstra算法)
yxc笔记
朴素dijkstra算法
1 2 3 4 5 6 7 思路: 定义距离数组,判断数组,存储结构 初始化距离和存储结构 第一个点的距离定义为0 取出最新点,找出最新点的最短距离 用该最短距离更新 判断长度,得出连不连通
时间复杂度是O(n²+m),n表示点数,m表示边数
初始化距离
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 int g[N][N]; int dist[N]; bool st[N]; int dijkstra () { memset (dist, 0x3f , sizeof dist); dist[1 ] = 0 ; for (int i = 0 ; i < n - 1 ; i ++ ) { int t = -1 ; for (int j = 1 ; j <= n; j ++ ) if (!st[j] && (t == -1 || dist[t] > dist[j])) t = j; for (int j = 1 ; j <= n; j ++ ) dist[j] = min (dist[j], dist[t] + g[t][j]); st[t] = true ; } if (dist[n] == 0x3f3f3f3f ) return -1 ; return dist[n]; } cin >> n>>m;memset (g,0x3f ,sizeof g);while (m--){ int a,b,c; cin>>a>>b>>c; g[a][b]=min (g[a][b],c); }int t =dijkstra ()l cout<<t;return 0 ;
堆优化版的dijkstra算法
手写堆
优先队列(不支持修改任意一个元素,里面的元素可能是m个,时间复杂度变为mlogm,但由于是稀疏图,所以和mlogn差不多
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 typedef pair<int , int > PII;int n; int h[N], w[N], e[N], ne[N], idx; int dist[N]; bool st[N]; void add (int a,int b,int c) { e[idx]=b,w[idx]= c,ne[idx]=h[a],h[a]=idx++; }int dijkstra () { memset (dist, 0x3f , sizeof dist); dist[1 ] = 0 ; priority_queue<PII, vector<PII>, greater<PII>> heap; heap.push ({0 , 1 }); while (heap.size ()) { auto t = heap.top (); heap.pop (); int ver = t.second, distance = t.first; if (st[ver]) continue ; st[ver] = true ; for (int i = h[ver]; i != -1 ; i = ne[i]) { int j = e[i]; if (dist[j] > distance + w[i]) { dist[j] = distance + w[i]; heap.push ({dist[j], j}); } } } if (dist[n] == 0x3f3f3f3f ) return -1 ; return dist[n]; }int main () { cin >> n>>m; memset (h,-1 ,sizeof h); while (m--){ int a,b,c; cin >> a >> b>>c; add (a,b,c); } int t = dijkstra (); cout<<t; return 0 ; }
BFS算法的局限性
dijkstra算法
dijkstra算法的时间复杂度
用于负权值带权图
6.4.4 最短路径问题(floyd算法)
floyd算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 cosnt int INF=1e9 ; 初始化: for (int i = 1 ; i <= n; i ++ ) for (int j = 1 ; j <= n; j ++ ) if (i == j) d[i][j] = 0 ; else d[i][j] = INF;void floyd () { for (int k = 1 ; k <= n; k ++ ) for (int i = 1 ; i <= n; i ++ ) for (int j = 1 ; j <= n; j ++ ) d[i][j] = min (d[i][j], d[i][k] + d[k][j]); }
floyd算法核心代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 for (int k = 0 ;k<n;k++){ for (int i = 0 ;i<n;i++){ for (int j = 0 ;j<n;j++){ if (a[i][j]>a[i][k]+a[k][j]){ a[i][j]=a[i][k]+a[k][j]; path[i][j]=k; } } } }
floyd算法示例
不能解决的问题
6.4.5 有向无环图描述表达式
有向无环图(DAG)
DAG描述表达式
例题
解题方法
6.4.6 拓扑排序
AOV网
yxc拓扑排序笔记
时间复杂度O(n+m),n表示点数,m表示边数
有向图才有拓扑序列,如果有环没有拓扑序,有向无环图一定有拓扑序列,也被称为拓扑图
入度是指有多少条边指向自己,因此所有入度为0的点都可以作为起点
出度是指有多少条边出去
把所有入度为0的点入队
一个有向无环图至少存在一个入度为0的点
有向无环图的拓扑序不一定是唯一的
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 int n,m;int h[N],e[N],ne[N],idx;int d[N],q[N];void add (int a,int b) { e[idx] = b; ne[idx] = h[a]; h[a]=idx++; }bool topsort () { int hh = 0 , tt = -1 ; for (int i = 1 ; i <= n; i ++ ) if (!d[i]) q[ ++ tt] = i; while (hh <= tt) { int t = q[hh ++ ]; for (int i = h[t]; i != -1 ; i = ne[i]) { int j = e[i]; d[j]--; if ( d[j] == 0 ) q[ ++ tt] = j; } } return tt == n - 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 #define size 100 typedef struct arcnode { int adjvex; struct arcnode *nextarc; }arcnode;typedef struct vnode { vertextype data; arcnode *firstarc; }vnode,adjlist[size];typedef struct { adjlist vertices; int vexnum,arcnum; }graph;bool topo (graph g) { init (s); for (int i = 0 ;i<g.vexnum;i++) if (indegree[i]==0 ) push (s,i); int count=0 ; while (!isempty (s)){ pop (s,i); print[count++]=i; for (p=g.vertices[i].firstarc;p;p=p->nextarc){ v=p->adjvex; if (!(--indegree[v])) push (s,v); } } if (count<g.vexnum) return false ; else return true ; }
逆拓扑排序
逆拓扑排序的实现
逆拓扑排序的实现(DFS算法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void dfstraverse (graph g) { for (v=0 ;v<g.vexnum;++v) visited[v]=false ; for (v=0 ;v<g.vexnum;++v) if (!visited[v]) dfs (g,v); }void dfs (graph g,int v) { visited[v]=true ; for (w=firstneighbor (g,v);w>=0 ;w=nextneighbor (g,v,w)) if (!visited[w]){ dfs (g,w); } print (v); }
6.4.7 关键路径
AOE网
关键路径
求关键路径的步骤
求所有事件的最早发生时间
求所有时间的最迟发生时间
求所有活动的最早发生时间
求所有活动的最迟发生时间
求所有活动的时间余量
求得关键活动、关键路径
关键活动、关键路径的特性
第七章 查找
7.1 查找的基本概念
对查找表的常见操作
查找算法的评价指标
7.2 顺序查找和折半查找
7.2.1 顺序查找
顺序查找的算法思想
顺序查找的实现
1 2 3 4 5 6 7 8 9 10 11 typedef struct { int *ad; int len; }table;int search (table st,int key) { int i; for (i = 0 ;i<st.len&&st.ad[i]!=key;++i); return i==st.len?-1 :i; }
顺序查找的实现(哨兵)
1 2 3 4 5 6 7 8 9 10 11 typedef struct { int *ad; int len; }table;int search (table st,int key) { st.ad[0 ]=key; int i; for (i=st.len;st.ad[i]!=key;--i); return i; }
查找效率分析
顺序查找的优化(对有序表)
用查找判定树分析ASL
顺序查找的优化(被查概率不相等)
7.2.2 二分查找
二分查找yxc
二分不用考虑有没有解
整数二分
有单调性的题目可以二分,但可以二分不一定有单调性,二分的本质不是单调性
二分的本质是性质,使得一部分满足红色性质,一部分满足绿色性质,整个区间可以一分为二,二分可以寻找性质的边界
①红点
mid=l+r+1>>1;
if(check(mid)) true ->[mid,r] l = mid;//mid满足红色性质
false->[l,mid-1] r = mid-1;//mid不满足红色性质,在绿色性质区域
②绿点
mid = l+r>>1;
if(check(mid)) true->[l,mid] r = mid;//mid满足绿色性质
false->[mid+1,r] l = mid+1;//mid满足红色性质,不满足绿色性质
1 2 3 4 5 6 7 8 9 10 11 12 13 bool check (int x) {} int bsearch_1 (int l, int r) { while (l < r) { int mid = l + r >> 1 ; if (check (mid)) r = mid; else l = mid + 1 ; } return l; }
1 2 3 4 5 6 7 8 9 10 11 12 int bsearch_2 (int l, int r) { while (l < r) { int mid = l + r + 1 >> 1 ; if (check (mid)) l = mid; else r = mid - 1 ; } return l; }
浮点数二分
注:判断是否满足绿色性质
1 2 3 4 5 6 7 8 9 10 11 12 13 bool check (double x) {} double bsearch_3 (double l, double r) { const double eps = 1e-6 ; while (r - l > eps) { double mid = (l + r) / 2 ; if (check (mid)) r = mid; else l = mid; } return l; }
二分查找的算法思想
二分查找的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 typedef struct { int *ad; int len; }table;int bi (table l,int key) { int low=0 ,high=l.len-1 ,mid; while (low<=high){ mid=(low+high)/2 ; if (l.ad[mid]==key) return mid; else if (l.ad[mid]>key) high=mid-1 ; else low=mid+1 ; } reuturn -1 ; }
查找效率分析
二分查找判定树的构造
二分查找的查找效率
7.2.3 分块查找
分块查找的算法思想
1 2 3 4 5 6 7 typedef struct { int maxvalue; int low,high; }index;int list[100 ];
![](/img/ 065908.jpg)
用二分查找索引
查找效率分析(ASL)
动态查找表
7.3 树形查找
7.3.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 typedef struct bstnode { int key; struct bstnode *l,*r; }bstnode,*bstree;bstnode *bst_search (bstree t,int key) { while (t!=NULL &&key!=t->key){ if (key<t->key)t=t->l; else t=t->r; } return t; }bstnode *bstsearch (bstree t,int key) { if (t==NULL ) return NULL ; if (key==t->key) return t; else if (key<t->key) return bstsearch (t->l,key); else retun bstsearch (t->r,key); }
二叉排序树插入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int insert (bstree &t,int k) { if (t==NULL ){ t=(bstree)malloc (sizeof (bstnode)); t->key=k; t->l=t->r==NULL ; return 1 ; } else if (k==t->key) return 0 ; else if (k<t->key) return insert (t->l,k); else return insert (t->r,k); }
二叉排序树的构造
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 int insert (bstree &t,int k) { if (t==NULL ){ t=(bstree)malloc (sizeof (bstnode)); t->key=k; t->l=t->r==NULL ; return 1 ; } else if (k==t->key) return 0 ; else if (k<t->key) return insert (t->l,k); else return insert (t->r,k); }void creat (bstree &t,int str[],int n) { t=NULL ; int i = 0 ; while (i<n){ insert (t,str[i]); i++; } }
二叉排序树的删除
查找效率分析
7.3.2 平衡二叉树
平衡二叉树的定义
1 2 3 4 5 6 typedef struct node { int key; int balance; struct node *l,*r; }node,*tree;
平衡二叉树的插入
调整最小不平衡子树
LL
![](/img/1 080957.jpg)
RR
LR
RL
代码思路
汇总
例子
练习1
练习2
练习3
查找效率分析
7.3.3 平衡二叉树的删除
平衡二叉树的插入&删除
平衡二叉树的删除
二叉排序树的删除操作
AVL树删除操作——例1
AVL树删除操作——例2
AVL树删除操作——例3
AVL树删除操作——例4
AVL树删除操作——例6
二叉排列树中,一个结点的前驱只需要从左孩子出发,一路往右下走到头即可
7.3.4 红黑树的定义和性质
为什么要发明红黑树
红黑树大概会怎么考
红黑树的定义
1 2 3 4 5 6 7 struct node { int key; node *parent; node *l; node *r; int color; };
实例:一棵红黑树
练习:是否符合红黑树要求
补充概念:结点的黑高
与黑高相关的推论
红黑树的性质
红黑树的查找
7.3.5 红黑树的插入
红黑树的插入
例子
7.3.6 红黑树的删除
红黑树的删除操作
7.4 B树和B+树
7.4.1 B树
B树的定义
B树的高度
7.4.2 B树的插入删除
B树的插入
总结
B树的删除
总结
7.4.3 B+树
B+树
B+树的查找
B+树 vs B树
![](/img/02 051648.jpg)
本质在于B+树重一个关键字对应一个分支,二B树当中n个关键字会对应n+1个分支
7.5 散列(hash)表
yxc笔记hash
Hash表
拉链法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 (1 ) 拉链法 int h[N], e[N], ne[N], idx; void insert (int x) { int k = (x % N + N) % N; e[idx] = x; ne[idx] = h[k]; h[k] = idx ++ ; } bool find (int x) { int k = (x % N + N) % N; for (int i = h[k]; i != -1 ; i = ne[i]) if (e[i] == x) return true ; return false ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 //寻找质数 for(int i = bool flag = true for(int j = 2 if(i%j= = 0 ){ flag = false break } if(flag) { cout<<i<<endl break } }
开放寻址法
只开一个数组,但是开的长度一般是输入数据的2-3倍
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 (2 ) 开放寻址法 int h[N];memset (h,0x3f ,sizeof h); int find (int x) { int t = (x % N + N) % N; while (h[t] != null && h[t] != x) { t ++ ; if (t == N) t = 0 ; } return t; } h[find (x)]=x;if (h[find (x)])== null cout<<"No" ;else cout<<"Yes" ;
字符串哈希
字符串前缀哈希法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 核心思想:将字符串看成P进制数,P的经验值是131 或13331 ,取这两个值的冲突概率低 小技巧:取模的数用2 ^64 ,这样直接用unsigned long long 存储,溢出的结果就是取模的结果typedef unsigned long long ULL; ULL h[N], p[N]; const int P=131 or 13331 p[0 ] = 1 ;for (int i = 1 ; i <= n; i ++ ) { h[i] = h[i - 1 ] * P + str[i]; p[i] = p[i - 1 ] * P; }ULL get (int l, int r) { return h[r] - h[l - 1 ] * p[r - l + 1 ]; }
哈希表
处理冲突的方法——拉链法
拉链法的小优化
哈希查找
常见的散列函数
除留取余法
直接定址法
数字分析法
平方取中法
处理冲突的方法——开放定址法
线性探测法
平方探测法
伪随机序列法
处理冲突的方法——再散列法
第八章 排序
8.1 排序的基本概念
排序算法的评价指标
排序算法的分类
8.2 插入排序
8.2.1 插入排序
插入排序
算法实现
1 2 3 4 5 6 7 8 9 10 11 12 void insert (int a[],int n) { int i,j,tmp; for (int i = 1 ;i<n;i++) if (a[i]<a[i-1 ]){ tmp=a[i]; for (j=i-1 ;j>=0 &&a[j]>tmp;--j) a[j+1 ]=a[j] a[j+1 ]=tmp; } }
算法实现(带哨兵)
1 2 3 4 5 6 7 8 9 10 11 void insert (int a[],int n) { int i,j; for (i=2 ;i<=n;i++) if (a[i]<a[i-1 ]){ a[0 ]=a[i]; for (j = i-1 ;a[0 ]<a[j];--j) a[j+1 ]=a[j]; a[j+1 ]=a[0 ]; } }
算法效率分析
优化——折半插入排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void insert (int a[],int n) { int i,j,low,high,mid; for (i=2 ;i<=n;i++){ a[0 ]=a[i]; low =1 ;high=i-1 ; while (low<=high){ mid=(low+high)>>1 ; if (a[mid]>a[0 ])high=mid-1 ; else low=mid+1 ; } for (j=i-1 ;j>=high+1 ;--j) a[j+1 ]=a[j]; a[high+1 ]=a[0 ]; } }
对链表进行插入排序
8.2.2 希尔排序
希尔排序
算法实现
1 2 3 4 5 6 7 8 9 10 11 12 13 void sort (int a[],int n) { int d,i,j; for (d=n/2 ;d>=1 ;d=d/2 ) for (i = d+1 ;i<=n;i++) if (a[i]<a[i-d]){ a[0 ]=a[i]; for (j=i-d;j>0 &&a[0 ]<a[j];j-=d) a[j+d]=a[j]; a[j+d]=a[0 ]; } }
算法性能分析
8.3 交换排序
8.3.1 冒泡排序
定义
算法实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void swap (int &a,int &b) { int tmp = a; a=b; b=tmp; }void sort (int a[],int n) { for (inti = 0 ; i<n-1 ;i++){ bool flag= false ; for (int j = n-1 ;j>i;j--) if (a[j-1 ]>a[j]){ swap (a[j-1 ],a[j]); flag=true ; } if (flag==false ) return ; } }
算法性能分析
冒泡排序是否适用于链表
8.3.2 快速排序
按照408的原题,一次划分只能确定一个元素的最终位置,而一趟排序可能可以确定多个元素的最终位置
yxc版
快速排序——分治 O(nlogn)-O(n²)
确定分界点:q[l],q[(l+r)/2],q[r];随机
调整区间:第一个区间所有的数都小于等于x,第二个区间所有的数都大于等于x
递归处理左右两端
快排非稳定,归并稳定(位置不发生变化)
暴力做法
a[],b[]
q[l-r] q[i]<=x x->a[]
q[i]>=x x->b[]
a[]->q[] b[]->q[]
1 2 3 4 5 6 7 8 9 10 11 12 13 void quick_sort (int q[], int l, int r) {if (l >= r) return ;int i = l - 1 , j = r + 1 , x = q[l + r >> 1 ]; while (i < j) { do i ++ ; while (q[i] < x); do j -- ; while (q[j] > x); if (i < j) swap (q[i], q[j]); } quick_sort (q, l, j), quick_sort (q, j + 1 , r); }
快排的定义
快排实现代码(教材版过于狗市,仅截图)
算法效率分析
稳定性
8.4 选择排序
8.4.1 简单选择排序
定义
算法实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void swap (int &a,int &b) { int tmp=a; a=b; b=tmp; }void sort (int a[],int n) { for (int i = 0 ;i<n-1 ;i++){ int min=i; for (int j = i+1 ;j<n;j++) if (a[j]<a[min])min=j; if (min!=i)swap (a[i],a[min]); } }
算法性能分析
稳定性
8.4.2 堆排序
yxc笔记
建堆:
建堆时间复杂度解释
堆的高度最高是logn层
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 int h[N], ph[N], hp[N], size;void heap_swap (int a, int b) { swap (ph[hp[a]],ph[hp[b]]); swap (hp[a], hp[b]); swap (h[a], h[b]); }void down (int u) { int t = u; if (u * 2 <= size && h[u * 2 ] < h[t]) t = u * 2 ; if (u * 2 + 1 <= size && h[u * 2 + 1 ] < h[t]) t = u * 2 + 1 ; if (u != t) { heap_swap (u, t); down (t); } }void up (int u) { while (u / 2 && h[u] < h[u / 2 ]) { heap_swap (u, u / 2 ); u >>= 1 ; } }for (int i = n / 2 ; i; i -- ) down (i); heap[++size]=x;up (size); heap[1 ]; heap[1 ]=heap[size]; size--;down (1 ); heap[k]=heap[size]; size--;down (k);up (k); heap[k]=x;down (k);up (k);
堆的定义
堆排序的定义
建立大根堆
建立大根堆(代码)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void heap (int a[],int len) { for (int i = len/2 ;i>0 ;i--) ad (a,i,len); }void ad (int a[],int k,int len) { a[0 ]=a[k]; for (int i = 2 *k;i<=len;i*=2 ){ if (i<len&&a[i]<a[i+1 ]) i++; if (a[0 ]>=a[i]) break ; else { a[k]=a[i]; k=i; } } a[k]=a[0 ]; }
基于大根堆进行排序
基于大根堆进行排序(代码)
1 2 3 4 5 6 7 8 9 10 11 12 void heap (int a[],int len) void ad (int a[],int k,int len) void heap (int a[],int len) { heap (a,len); for (int i = len;i>1 ;i--){ swap (a[i],a[1 ]); ad (a,1 ,i-1 ); } }
算法效率分析
稳定性
8.4.3 堆的插入删除
在堆中插入新元素
在堆中删除元素
8.5 归并排序、基数排序和计数排序
8.5.1 归并排序
yxc
归并排序——分治 O(nlogn)
确定分界点:mid=(l+r)/2
递归排序left,right
归并——合二为一O(n)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void merge_sort (int q[], int l, int r) { if (l >= r) return ; int mid = l + r >> 1 ; merge_sort (q, l, mid); merge_sort (q, mid + 1 , r); int k = 0 , i = l, j = mid + 1 ; while (i <= mid && j <= r) if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ]; else tmp[k ++ ] = q[j ++ ]; while (i <= mid) tmp[k ++ ] = q[i ++ ]; while (j <= r) tmp[k ++ ] = q[j ++ ]; for (i = l, j = 0 ; i <= r; i ++, j ++ ) q[i] = tmp[j]; }
归并排序的定义
2路归并
4路归并
归并排序(手算模拟)
代码实现
算法效率分析
8.5.2 基数排序
定义
算法效率分析
r指的是每一个关键字位有可能取多少种取值
d指的是关键字可以被拆分成几个部分
稳定性
基数排序的应用
8.7 外部排序
8.7.1 外部排序
外存、内存之间的数据交换
外部排序原理
每当一个输入缓冲区空了以后,需要立即把与之对应的那个归并段下一块的内容给读入内存,然后才能接着往下归并
时间开销分析
优化:多路归并
优化:减少初始归并段数量
纠正:什么是多路平衡归并
8.7.2 败者树
多路平衡归并带来的问题
败者树的构造
败者树的使用
败者树在多路平衡归并中的应用
败者树的实现思路
8.7.3 置换-选择排序
土办法
置换-选择排序
8.7.4 最佳归并树
归并树的神秘性质
构造2路归并的最佳归并树
多路归并的情况
多路归并的最佳归并树
添加虚段的数量