数据结构学习笔记(一)简单线性结构
数据结构在计算机领域是一门十分重要的学科,但是同时也是一门很枯燥的学科。
作为一个非计算机专业生,面对这一门课的时候更是相当头痛,在自学的过程中,之前很多人提到了浙江大学的陈越。于是特地在中国大学MOOC上搜到了浙江大学的数据结构课程,坚持着勉强看完了。
不得不说,浙江大学的计算机课程是真的强!之前我看浙江大学翁恺老师的C语言课程时,就已经被圈粉。这次的数据结构自然也不会失望,引用了很多经典的训练题,配上PTA的题库,再加上何钦铭教授独特的嗓音。这门课对于计算机专业的学生是是非常好的。
附上某大神的GitHub笔记https://github.com/callmePicacho/Data-Structres
但是,这门课是有一定难度的,尤其是对于向我这种基础不好的同学,很多过程要停下来想好久。所以我学这门课的时候比较痛苦。
后来我在B站上找到了up主@青岛大学—王卓的课程,是老师自己录制的,讲的比较详细一些,适合我这种基础比较差的人,弄懂了很多之前一直卡着的问题。可能是这个老师不常玩B站的原因,几百集课程没有上传到同一个bv号里。但是可以通过这一篇专栏找到https://www.bilibili.com/read/cv2306631
类似的,郝斌老师的数据结构课程同样也是以清晰易懂而著名,但是——年代有些久远,画质太不清晰了,设备也比较老旧了,看起来比较费劲。
综上,这里的笔记就按照青岛大学王卓老师的课程为参考,语言为C/C++
数据结构的知识体系目录如下,课程的目录也是按照这样的顺序

万能公式:
程序=数据结构+算法
关于数据结构的各种概念定义什么的,虽然考试一定会考,但实际真的用不到,相信很多人也讨厌背这些,只要记住几个关键的知识点就行了,比如
数据>数据元素>数据项
数据结构大体上可以分为线性和分线性两种,具体可以分为逻辑结构和存储(物理)结构,分别为

可以看出,数据结构学科主要学的是逻辑结构,物理结构则主要是属于计算机硬件和操作系统相关的知识。
在数据结构学习过程中,常常会用到ADT(Abstract Data Type)也就是,抽象数据类型,包括D、S、P三部分
ADT抽象数据类型名{
(D)数据对象{}
(S)数据关系{}
(P)基本操作{}
}
线性表
顺序表示
线性表的顺序表示主要是用数组来实现,有这四个最明显的特点:
地址连续、依次存放、随机存取、类型相同
1、表示方法
typedef struct { ElementType elem[MaxSize]; int length;//当前长度 }Sqlist L;//静态 //或者 typedef struct { ElementType *elem; int length; }Sqlist L;//动态
2、线性表的初始化
Status InitList_sq(Sqlist &L){ L.elem = new ElementType[MaxSize];//为元素分配空间 if(!L.elem)exit(OVERFLOW);//分配失败 L.length = 0; return OK; }
3、销毁线性表,释放空间
void DestroyList(Sqlist &L){ if(L.elem)free L.elem;//C++用delete }
4、清空线性表
void ClearList(Sqlist &L){ l.length = 0;//长度值为0 }
5、求线性表的长度
int GetLength(Sqlist L){ return L.length; }
6、判断线性表是否为空
int IsEmpty(Sqlist L){ if(L.length==0)return 1; else return 0; }
7、顺序表的取值,根据位置i获取相应位置元素的内容
int GetElem(Sqlist L,int i,ElementType &e){ if(i<1 || i>L.length)return ERROR;//判断i值是否合理 e = L.elem[i-1]; return OK; }
8、顺序表的查找,在顺序表中查找与e值相同的元素,并返回相应的位置
int LocateElem(Sqlist L,ElementType e){ //在线性表中查找值为e的元素,返回所在的序号 int i=0; while(i<L.length && L.elem[i]!=e) i++; if(i<L.length) return i+1;//查找成功,返回其序号 else reutrn 0;//查找失败 }
9、顺序表的插入
Status ListInsert(Sqlist L,int i,ElementType e){ if(i<1 || i<L.length+1)return ERROR;//超出范围 if(L.length==MaxSize)return ERROR;//存储空间已满 for(int j=L.length-1;j>=i-1;j++) L.elem[j+1]=L.elem[i];//元素后移 L.elem[i-1]=e; L.length++; return OK; }
10、顺序表的删除
Status ListDelete(Sqlist L,int i){ if(i<1 || i>L.length)return ERROR;//判断范围是否合法 for(int j=i;j<=L.length;j++) L.elem[j-1] = L.elem[j];//元素前移 L.length--; return OK; }
线性表在取值,求长度这方面是非常方便的,因为有下标的缘故,一行代码就可以搞定。但是在插入和删除元素的时候就比麻烦了,需要移动大量元素,复杂度为O(n)
单链表
链表的结构除了每一个节点的数据和指针,还会有一个头指针,用来指定第一个节点元素的位置。但是除此之外,还有一种带头节点的链表,是一个附设的节点

关于这个头节点存在的意义,主要有两点
1、首元节点的位置保存在头节点的指针域中,与其他位置一样,不需要做特殊处理。
2、便于非空表的处理,无论是否为空,头指针都是指向非空,便于统一处理。
具体可参考https://www.jianshu.com/p/580ddaca13d5
链表的特点有:
1、物理位置书是随机不连续的,可不连续
2、只能通过头节点进入链表,然后依次向后找。即顺序存取
以下是相关操作
1、单链表表示方法
typedef struct Lnode{ ElementType data; struct Lnode *next;//下一个节点的地址 }Lnode,*LinkList;
2、单链表初始化
Status InitList(LinkList &L){ L = new Lnode;//头节点 L->next = NULL; return OK; }
3、单链表的销毁
Status DestroyLnode(LinkList &L){ Lnode *p; while(L){ p=L; L=L->next; delete p; } }
4、清空单链表
Status ClearList(LinkList &L){ Londe *p,*q; p = L->next; while(p){ q=p->next; delete p; p=q; } L->next = NULL; return OK; }
5、求单链表的长度
int ListLength(LinkList L){ LinkList P; p = L->next;//p指向第一个节点 int i=0; while(p){ //遍历链表,统计节点数 i++; p = p->next; } return i; }
6、查找元素,三种情况
Lnode *LocateElem(LinkList L,ElementType e){ //在链表中找到值为e的元素,并返回其地址 p = L->next; while(p && p->data!=e){ p=p->next; } return p; } int LocateElem(LinkList L,ElementType e){ //在链表中查找值为e的元素,返回其序号;否则返回0 p = L->next; int j=1; while(p && p->data!=e){ p = p->next; j++; } if(p)return j; else return 0; } Status GetElem(LinkList L,int i,ElementType &e){ //获取链表中第i个元素的内容,通过e带出 p = p->next; int j=1; while(p && j<i){ p = p->next; ++j; } if(!p || j>i)return ERROR; e = p->data; return OK; }
7、在特定位置插入元素
Status ListInsert(LinkList &L,int i,ElementType e){ //在链表的第i个元素之前插入数据元素e p=L; int j=0; while(p && j<i-1){//寻找i-1个节点,让p指向它 p = p->next; ++j; } if(!p || j>i-1)reutrn ERROR; s=new Lnode;s->data=e;//把e放到新建节点中 s->next = p->next; p->next = s; return OK; }
8、在特定位置删除节点
Status ListDelete(LinkList &L,int i,ElementType &e){ //将链表中第i个元素删除,用e记录下来 p=L; int j=0; while(p->next && j<i-1){//寻找第i个节点,p指向其前驱节点 p = p->next; ++j; } if(!p->next || j>i-1)return ERROR; q = p->next;//将要删除的节点临时保存在q p->next = q->next;//删除节点的前驱指向其后驱 e=q->data;//把删除节点的数据值记录在e delete q; return OK; }
9、构建链表,分为前头法和尾插法
void CreateList_H(LinkList &L,int n){ //头插法,新的节点插到首元节点前面,头节点后面 L=new Lnode; L->next = NULL; for(i=n;i>0;--i){ p = new Lnode; cin>>p->data;//scanf(&p->data) p->next = L->next; L->nxet = p;//把新节点插到表头,头节点后面 } } void CreateList_T(LinkList &L,int n){ //尾插法,新的节点插到链表的末端 L = new Lnode; L->next = NULL; LinkList r = L;//r用来当尾节点,先指向头节点 for(int i=0;i<n;i++){ p =new Lnode; cin>>p->data; p->next = NULL;//新建节点的指针指为空 r->next = p;//新建节点接到尾指针后面 r = p; //新的节点成为了尾节点,赋给r } }
链表这种结构在插入删除的时候比较方便,改一下指针比较方便。但是,定位却是一个很麻烦的事情,因为必须从头节点开始,一个一个找。特定位置的操作必须先遍历一次链表。比如构建链表,尾插法虽然好理解,但是操作起来会更复杂,需要多定义一个指针;头插法不好理解,但是操作简单,每次都在头节点后操作,只需要多定义一个指针就可以。
以下有几个简单的案例
线性表的合并,去除重复的元素
void Union(LinkList &La,LinkList Lb){ //合并两个线性表,Lb合并到La中,把重复的元素去掉 La_len = ListLength(La); Lb_len = ListLength(Lb); for(int i=0;i<Lb_len;i++){ GetElem(Lb,i,e); if(LocateElem(La,e)) ListInsert(&La,++La_len,e); } }
两个有序表的=合并,要求合并之后依然保持有序
//用线性表实现 void MergeList_Sq(Sqlist La,Sqlist Lb,Sqlist &Lc){ //两个有序表的合并,成为一个新的有序表 pa = La.elem; pb = Lb.elem;//分别指向第一个元素 Lc.length = La.length + Lb.length;//新表的长度等于两数组长度之和 Lc.elem = new ElementType[Lc.length];//分配数据空间 pc = Lc.length; pa_last = La.elem + La.length - 1; pb_last = Lb.elem + Lb.length - 1;//指向最后节点的指针 while(pa<=pa_last && pb<=pb_last){//两个表都非空的条件 if(*pa<=*pb) *pc++ = *pa++; else *pc++ = *pb++;//把较小的那一个接到新表的后面 } while(pa<=pa_last)*pc++ = *pa++;//La非空,就把La的剩余部分接上去 while(pb<=pb_last)*pc++ = *pb++;//Lb非空,就接上Lb } //链表实现 void MergeList_L(LinkList &La,LinkList &Lb,LinkList &Lc){ pa = La->next; pb = Lb->next;//分别指向首元节点 pc = Lc = La;//La的头节点作为Lc的头节点 while(pa && pb) { if (pa->data <= pb->data) { pc->next = pa;//接上pa pc = pc->next;//pc向后移 pa = pa->next;//pa向后移 } else { pc->next = pb; pc = pc->next; pb = pb->next; } } pc->next = (pa?pa:pb); delete Lb; }

浙公网安备 33010602011771号