数据结构学习笔记(一)简单线性结构

数据结构在计算机领域是一门十分重要的学科,但是同时也是一门很枯燥的学科。


作为一个非计算机专业生,面对这一门课的时候更是相当头痛,在自学的过程中,之前很多人提到了浙江大学的陈越。于是特地在中国大学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;
}

 

posted @ 2020-09-04 19:25  fallwinddy  阅读(492)  评论(0)    收藏  举报