数据结构学习
第一章 序言
算法的时间复杂度
一个问题的规模是n,解决这一问题的某一算法所需时间是T(n)也是n的某一函数,T(n)就称为算法的时间复杂度。
算法的空间复杂度
算法的空间复杂度是指算法在计算机内执行时所需存储空间的变量,存储空间具体是指编写程序时,程序的存储空间、变量占用空间、系统堆栈的使用空间等。
数据结构的4种基本结构
集合结构、线性结构、树形结构、图形结构
第二章 线性表
线性表是由同一类型的数据元素构成的一种线性的数据结构(仅作为一种抽象的数据结构,可由数组、链表等构成)
基本操作
ListInit(L) ListLength(L) ListGet(L,i) ListLocate(L,x) ListPrior(L,e) ListNext(L,e)
ListInsert(L,i,e) ListDelete(L,i) ListEmpty(L) ListClear(L)
相关操作
线性表的遍历、线性表的合并 (1)直接合并 (2)保序合并
线性表的顺序存储结构(存储空间连续,可用一组数组来实现线性表的顺序存储)
顺序存储结构分析 1.遍历和定位操作都是以线性时间执行,取元素只需花费常数时间,非常合适于那些需要经常进行查找操作的情况;2.顺序操作的空间是静态分配的需要事先指定MAXSIZE的大小,因此在事先不知道线性表大小的情况下,很容易造成空间浪费;3.插入和删除操作花费昂贵。
线性表的链式存储结构(这种存储允许不连续的存储)
链表:是一种由结点组成的数据结构,每个结点都包含数据元素信息和指向链表中另一结点的指针。单链表有带头结点的单链表和不带头结点的单链表。
单链表结点声明和链表初始化
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//单链表结构声明 struct singly_linked_list { int x; struct singly_linked_list* next; } //带头结点的单链表初始化 void Init_struct_head(singly_linked_list* L) { L= new singly_linked_list; if(L==NULL) { cout<<"申请失败"<<endl; exit(0); } L->next=NULL; } //不带头结点的单链表初始化 void Init_struct_nonhead(singly_linked_list* L) { L= NULL; }
带头结点链表的操作
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//链表求表长 int LinkedListLength(singly_linked_list* L) { if(L==NULL) { cout<<"链表无效"<<endl; exit(0); } singly_linked_list* p; p=L->next; int length = 0; while(p!=NULL) { ++length; p=p->next; } return length; } //带头结点的取元素操作 singly_linked_list* LinkedListGet(singly_linked_list* L, int i) { singly_linked_list* p= L->next; for(;i>1&&p!=NULL;--i,p=p->next); if(i=1) return p; else { cout<<"i is too large for this list"<<endl; exit(0); } //单链表的定位操作 int LinkedListGet(singly_linked_list* L, int i) { singly_linked_list* p = L->next; int pos=1; while(p!=NULL&&p->x!=i) { p=p->next; } if (p!=NULL) return pos; else { cout<<"there is no such an element\n"; return -1; } } //带头单链表的插入 void LinkedListInsert(singly_linked_list* L, singly_linked_list* InsertL, int i) { //i is an element which needs inserting singly_linked_list* p = new singly_linked_list; if(p==NULL) { cout<<"Applying apaces failed\n"; exit(0); } p->x=i; singly_linked_list* tmp = L; while(tmp->next!=NULL&&tmp->next!=InsertL) { tmp=tmp->next; } if(tmp->next=NULL) { cout<<"there is no node to insert\n"; exit(0); } else { q->next = tmp->next; tmp->next = q; } return; } //带头单链表删除 void LinkedListDelete(singly_linked_list* L, singly_linked_list* DelL) { singly_linked_list* tmp = L; singly_linked_list* p; while(tmp->next!=NULL&&tmp->next!=DelL) { tmp=tmp->next; } if(tmp->next=NULL) { cout<<"there is no node to delete\n"; exit(0); } else { p = tmp->next; tmp->next = p->next; delete p; p=NULL; } return; } //带头单链表的合并 void LinkedListMerge(singly_linked_list* La, singly_linked_list* Lb, singly_linked_list* Lc) { singly_linked_list* p, pa, pb; if(La==NULL||Lb==NULL) { cout<<"invalid pointer to list\n"; exit(0); } pa=La->next; pb=Lb->next; Lc=La; p=Lc; while(pa!=NULL&&pb!=NULL) { if(pa->x<=pb->x) { p->next = pa; p = p->next; pa = pa->next; } else { p->next = pb; p = p->next; pb = pb->next; } } if(pa==NULL) { p->next = pb; } else { p->next = pa; } delete Lb; }
双向链表
循环链表
链式存储结构分析
1)存储空间是动态分配的 2)便于实现插入和删除 3)内容分散导致调试不方便 4)每个结点既有数据又有指针域,增加存储开销 5)链表中查找结点时,需从头结点开始增加了时间
小结:线性表特点:集合中必存在唯一的一个“第一个元素”和“最后一个元素”,唯一前驱和后继。
第三章 栈和队列
栈是限定仅在表尾进行插入和删除操作的线性表。LIFO
基本操作:
StackInit(s) StackEmpty(s) Pop(s) Push(s,x) StackGetTop(s) StackClear(s) StackLength(s)
栈操作的实现过程中,应注意错误检测
顺序栈、链栈
顺序栈和链栈和线性表中的顺序表和链表的优缺点分析类似
对于链栈的优化:
为避免new和delete带来的昂贵开销,通过使用第二个栈来回收和二次使用弹出的结点
顺序栈的操作
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//顺序栈的声明 struct node { ElemType data[MAXSIZE]; int top; } //顺序栈的初始化 void SeqStackInit(SeqStack q) { q.top = -1; } //顺序栈的判栈空操作 bool SeqStackEmpty(SeqStack q) { if(q.top==-1) return TURE; return FALSE; } //如栈操作 void SeqStackPush(SeqStack q,ElemType x) { if(q.top == MAXSIZE-1) { cout<<"full stack\n"; exit(0); } ++q.top; q.data[q.top]=x; } //出栈操作 ElemType SeqStackPop(SeqStack q) { if(q.top == -1) { cout<<"full empty\n"; exit(0); } ElemType x = q.data[q.top]; top--; return x; } //取栈顶元素 ElemType SeqStackGetPop(SeqStack q) { if(q.top == -1) { cout<<"full empty\n"; exit(0); } return q.data[q.top]; }
用栈写一个后缀式表达式计算
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
void PosfixCompute() { string posfix; cin>>posfix; for(int i = 0;i < posfix.length() , ++i) { if(posfix[i]>'0'&&posfix[i]<'9') { push(q,posfix[i]); continue; } switch posfix[i] { case '*': ElemType a,b,c; a=pop(); b=pop(); c=a*b; push(q,c); break; case'/': ElemType a,b,c; a=pop(); b=pop(); c=b/a; push(q,c); break; case'+': ElemType a,b,c; a=pop(); b=pop(); c=b+a; push(q,c); break; case'-': ElemType a,b,c; a=pop(); b=pop(); c=b-a; push(q,c); break; } } cout<<"compute the result is "<<pop(q); return; }
中缀式转后缀式的时候用一个栈专门存放运算符,遇到运算级别的低的要压入时就需要pop直到top上的运算符级别低于要压入的。遇到)直接pop到(。具体代码此处不详细写
函数调用
在进行函数调用时,系统将所需要的信息存放在栈中,如函数的局部变量、返回值。在系统中,每个函数的状态是由函数中的局部变量、函数参数值、函数的返回值地址决定的。存储这些信息的数据区域称为活动记录,或叫做栈帧,它是运行时系统栈上分配的空间。函数退出时才能释放空间。
活动记录所包含的信息
1)函数所有参数的值 2)函数中的局部变量 3)返回地址 4)非void类型的返回值
递归与栈
递归是指一个直接调用自己或者通过一系列的过程调用语句间接地调用自己的过程
递归的调用会不断地消耗栈空间,实际计算机中的栈通常是从内存分区的高端向下增加
在实际编写代码过程中,在算法的设计阶段使用递归算法,并证明算法是正确的。在算法的编码阶段,采用迭代和栈技术实现非递归算法
队列
队列属于线性表,只允许在表的一端插入,在另一端删除。允许插入的一端称为队尾,允许删除的一端称为对头
基本操作:
QueueInit(Q) QueueEmpty(Q) EnQueue(Q,x) DeQueue(Q) QueueGetHead(Q) QueueClear(Q) QueueLength(Q)
顺序队列 循环队列 链队列
队列应用
缓冲区:是一个内存区域,它起到将数据从源地点复制到目的地点时的过渡作用。缓冲区主要用来保证高速部件和低速部件的速度匹配问题
杨辉三角公式代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
void PrintYanghui (LinkedQueue Q, int n) { EnLinkedQueue(Q,1); for(i = 0; i < n; ++i) { int s = 0; EnLinkedQueue(Q,0); for(int j = 1; j<i+2;++j) { int x= DeLinkQueue(Q); EnLinkedQueue(Q,x+s); s =x; if(j!=i+2) cout<<s<<'\t'; } cout<<endl; } }
第四章 串
由零个或多个字符组成的有限序列
子串、主串的概念
子串在主串中的位置,由子串的第一个字符在主串中的位置来表示。串在程序中分为串常量和串变量,串变量可改变。串常量只能被引用而不能改变,const定义。
string s; s = "How are you"; s = "Fine"; //correct const string s; s = "How are you"; s = "Fine"; //wrong!不能再赋值
基本操作:
StringAssign(S,T) StringEqual(S,T) StringLength(S) StringContact(S,T) SubString(S, start, length)
Index(S, T) Replace(S, T, V) StringInsert(S, start, T) StringDelete(S, start, length)
串的顺序存储结构
顺序串类型声明
1)#define MAXSIZE = 256;
char String[MAXSIZE]
2)struct Node {
char* str;
int length;
}
第二种存储方式,采用动态分配的一维数组存储串,这种存储方式可以利用标准函数new 和 delete 动态地分配和释放空间,并且以一个特殊的字符('\0') 作为字符串的结束标志。串的赋值将串下的值赋给S,如果串S非空时需要将串的内存释放掉再进行赋值操作。
串的连接,将串T连接到串S的后面,进行连接操作时要将串S中的元素先暂存起来,然后进行空间申请,否则会造成串S的内容丢失。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
void SeqStringAssign(String* S, String* T) { if(S->str) delete S->str; S->length = T->length; if(S->length == 0) { s->str = new char; if(S->str==NULL) { cout<<"spaces failed"<<endl; exit(0); } s->str = '\0'; } else { S->str = new char[S->length+1]; if(S->str==NULL) { cout<<"spaces failed\n"; exit(0); } for(int i= 0;i<S->length; ++i) { S->str[i] = T->str[i]; } S->str[S->length] = '\0'; } }
子串定位算法(模式匹配算法) 子串称为模式串,主串成为目标串
简单模式匹配算法 时间复杂度o(mn) 主串S为n长
KMP算法 时间复杂度o(m+n) 子串为m长
Boyer-Moor算法 Monte-Carlo算法(指纹)(模糊匹配)
KMP代码待写
第五章 数组及广义表
二维数组中每一个元素都有两个直接前驱或两个直接后继
多维数组的基本操作
InitArray(&A, bound1, bound2, ...,boundns)
Value(A, &e, index1, ..., indexn)
Assign(&A, e, index1, ..., indexn)
数组的顺序存储方式的存储方式
BASIC PL/1 PASCAL 和C语言都采用以行序为主的存储方式
FORTRAN采用以列序为主的存储方式
矩阵的压缩存储
特殊矩阵:对称矩阵、三角矩阵、对角矩阵
稀疏矩阵:delta = t/(m*n), delta<=0.05时称为稀疏矩阵
稀疏矩阵不只记录下非零元素的值,还把它的下标也一起记录下来
这种存储方式为三元组
十字链表
广义表:广义表中的数据元素可以具有不同的数据结构 GL = (d1,d2,...,dn)
di为单个元素则称其为原子,如果di为广义表则称其为GL的子表,长度.深度定义
广义表的存储采用链式存储结构来存储广义表
第六章 树和二叉树
树的一些基本术语
度(degree) 叶子(leaf) 分支结点 孩子 双亲/父亲 兄弟 路径
路径的长度 祖先 子孙 层数/树的高度/深度 堂兄弟 有序树
无序树 森林 二叉树 满二叉树 完全二叉树
具有n个结点的完全二叉树的深度为[log2 n]+1
二叉树存储:顺序存储 链式存储结构
遍历:前序、中序、后序遍历
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
void PreOrderTraverse(BiTree BT) { if(BT) { cout<<BT->data<<'\t'; PreOrderTraverse(BT->lchild); PreOrdertRaverse(BT->rchild); } } void PreOrderNoRec(BiTree BT) { Stack S; BiTree p = BT; while((NULL!= p)||!StackEmpty(S)) { if(NULL!=p) { cout<<p->data<<'\t'; Push(S,p); p=p->lchild; } else { p = Top(S); Pop(S); p = p->rchild; } } } void InOrderTraverse(BiTree BT) { if(BT) { InOrderTraverse(BT->lchild); cout<<BT->data<<'\t'; InOrderTraverse(BT->rchild); } } void InOrderNoRec(BiTree BT) { BiTree p = BT; Stack S; while ((NULL!=p)||!StackEmpty(S)) { if(NULL!=p) { Push(S,p); p=p->lchild; } else { p = Top(S); Pop(S); cout<<p->data<<'\t'; p = p->rchild; } } } void PostOrderTraverse(BiTree BT) { if(BT) { PostOrderTraverse(BT->lchild); PostOrderTraverse(BT->rchild); cout<<BT->data<<'\t'; } } void PostOrderNoRec(BiTree BT) { Stack S; Stack Tag; BiTree p = BT; while((NULL!=p)||!StackEmpty(S)) { while(NULL!=p) { Push(S,p); Push(Tag,0); p = p->lchild; } if(!StackEmpty(S)) { if(Pop(tag) !=1) { p = Top(S); Push(tag.1); p=p->rchild; } else { p = Top(S); Pop(S); cout<<p->data<<'\t'; } } } }
未完,待续(查找和排序及红黑树)