数据结构笔记1:链表

数据结构笔记1:链表

链表主要需要掌握,头插法尾插法创建链表。插入删除操作。还有就是遍历了,

很简单。

最关键的一点就是,考虑好头节点的应用。

Node.h

#ifndef _NODE_H_
#define _NODE_H_
//结构体一般定义在.h文件中
#define ElemType int
typedef struct LNode //单链表节点
{
    ElemType data;
    struct LNode *next;
}LNode,*LinkList;
/*与上面等价
struct LNode{
    ElemType data;
    struct LNode *next;
};
typedef struct LNode LNode;  //LNode就是 struct LNode的一个别名
typedef struct LNode*  LinkList; //LinkList是struct LNode*的一个别名

LNode a;//声明了一个struct LNode型变量a,与写 struct LNode a; 等价;
LinkList p;//声明了一个struct LNode *型指针变量p,与写 struct LNode *p; 等价。
*/
typedef struct DNode //双链表节点
{
    ElemType data;
    struct DNode *prior,*next;
}DNode,*DLinklist;
#endif

LinkList.h

#ifndef _LINKLIST_H_
#define _LINKLIST_H_
#include"Node.h"
using namespace std;
//单链表
LinkList LinkList_HeadInsert_Init(LinkList &L);
LinkList LinkList_TailInsert_Init(LinkList &L);
void LinkList_All_Out(LinkList L);
LinkList LinkList_Search(LinkList L,int i);
void LinkList_Insert(LinkList L,int i);
void LinkList_Delete(LinkList L,int i);
int LinkList_GetLenth(LinkList L);
//双链表
DLinklist DLinklist_Init(DLinklist &D);
void DLinklist_All_Out(DLinklist D);
DLinklist DLinklist_Search(DLinklist D,int i);
void DLinklist_Insert(DLinklist D,int i,bool j);
//这次写的函数都是传入的头结点,但是事实上很多情况下需要传入的
//是中间某个节点返回的p指针,比如先用search函数得到对应的指针。
#endif

LinkList.cpp

#include <iostream>
#include "LinkList.h"
#include "Node.h"
using namespace std;
/****************************单链表*********************************/

//最核心的思想,头结点的处理如何进行
//头插法,但是得到的元素和输入的元素是倒序的
LinkList LinkList_HeadInsert_Init(LinkList &L)
//引用,改变函数内L的值函数返回值也发生改变,同时引用定义了L,无需重新定义可直接调用
//需要对传入的参数进行改变时就要用引用
{
    LinkList p;
    int x;
    L = new(LNode); //头结点,不存放数据 ,
    //分配的内存是用来存放节点的,然后让一个指针指向这段内存。
    L->next = NULL;
    cin >> x;
    while(x != 9999)
    {
        p = new(LNode); //创建新节点
        p->data = x;
        p->next = L->next; //插在头结点和现在第一个节点之间变成第一个节点
        L->next = p;
        cin >> x;
    }
    return L ;
}
//尾插法
LinkList LinkList_TailInsert_Init(LinkList &L)
{
    int x;
    LinkList p,r; //尾插法需要多定义一个指针作为中间变量
    L = new(LNode);//要先分配空间,再把L赋给r
    r = L;
    cin >> x;
    while(x != 9999)
    {
        p = new(LNode); //创建新节点
        p->data = x;
        r->next = p;
        r = p;
        cin >> x;
    }
    r->next = NULL;//尾插法让最后一个节点指向空即可
    return L;
}
//遍历所有元素并全部输出
void LinkList_All_Out(LinkList L)
{
    LinkList p = L->next;
    while(p != NULL)
    {
        cout<< p->data <<endl;
        p = p->next;
    }
}
//访问特定元素并输出
LinkList LinkList_Search(LinkList L,int i)
{
    LinkList p = L->next;
    int j = 1;
    if(i == 0) return L;
    if(i<1) return NULL;
    while(p->next) //判断条件(p->next)为真等价于判断(p->next != NULL)为真
    {
        if(j == i) break; //遍历+下标
        j++;
        p = p->next;
    }
    return p;
}
void LinkList_Insert(LinkList L,int i) //插入节点
{
    LinkList p,q;
    int x;
    p = LinkList_Search(L,i);
    q = new(LNode);
    cin >> x ;
    q->data = x;
    q->next = p->next;
    p->next = q;
}
void LinkList_Delete(LinkList L,int i)
{
    int j = 1;
    LinkList p,q;
    p = L;
    while(p && j < i)
    {
        j++;
        p = p->next;
    }
    q = p->next; //这里为什么要把p->next保存给q呢
    //因为我们最后要把删去的节点对应的内存给释放,注意释放的是指针指向的内存而并非指针本身。
    //而执行了 p->next = p->next->next 之后,我们就无法找到该节点这段内存了,用q保存p->next,
    //本质上保存的是这段内存,因此最后直接释放q,就相当于释放掉这段内存了。初始化q时也无需给q分配内存。
    p->next = p->next->next;
    delete q; //delete()和free()的操作对象是指针,清除的是指针指向的内存。

}
int LinkList_GetLenth(LinkList L)
{
    LinkList p = L->next;
    int n = 0;
    while(p)
    {
        n++;
        p = p->next;
    }
    return n;
}
/**************************************************************/

/**************************双链表*******************************/
DLinklist DLinklist_Init(DLinklist &D)
{
    DLinklist p,q;
    int x;
    D = new(DNode);//建空双向循环链表
    D->next = D;
    D->prior = D;
    q = D;
    cin>>x;
    while(x != 9999)
    {
        p = new(DNode);
        p->prior = q;
        q->next = p;
        p->next = D;
        D->prior = p;
        p->data = x;
        q = p;
        cin>>x; 
    }
    return D;
}
void DLinklist_All_Out(DLinklist D)
{
    DLinklist p = D->next;
    while(p != D)
    {
        cout<< p->data <<endl;
        p = p->next;
    }
}
DLinklist DLinklist_Search(DLinklist D,int i)
{
    DLinklist p = D->next;
    int j = 1;
    if(i == 0) return D;
    if(i<1) return NULL;
    while(p->next) //判断条件(p->next)为真等价于判断(p->next != NULL)为真
    {
        if(j == i) break; //遍历+下标
        j++;
        p = p->next;
    }
    return p;
}
void DLinklist_Insert(DLinklist D,int i,bool j) //插入节点,bool为0,
//插入在i节点之后,为1插在之前
{
    DLinklist p,q;
    int x;
    p = DLinklist_Search(D,i);
    q = new(DNode);
    cin >> x ;
    q->data = x;
    if(j == 0) //后插
    {
        p->next->prior = q;//先链接新增节点和未被移动指针p指向的节点
        q->next = p->next; //1和2指令可对调,3和4指令可对调,
        //但12要在34之前执行
        p->next = q;
        q->prior = p;
    }
    else //前插
    {
        p->prior->next = q;
        q->prior = p->prior;
        p->prior = q;
        q->next = p;
    }   
}
void DLinklist_Delete(DLinklist D,int i) //删除访问到的那个节点
{
    int j = 1;
    DLinklist p,q;
    p = D;
    while(p && j < i)
    {
        j++;
        p = p->next;
    }
    q = p->next; 
    p->next = p->next->next;
    delete q;
}

main.cpp

#include <iostream>
#include "LinkList.h"
using namespace std;
// int main()
// {
//    LinkList L;
//    int num;
//    //L =  LinkList_HeadInsert_Init(L); //引用时在定义里用到了&L,因此在调用函数时直接将L传参进去即可
//    L =  LinkList_TailInsert_Init(L);
//    //LinkList_All_Out(L);
//    //p = LinkList_Search(L,5);
//    //cout<<p->data<<endl;
//    LinkList_Insert(L,5);
//    LinkList_All_Out(L);
//    LinkList_Delete(L,4);
//    LinkList_All_Out(L);
//    num = LinkList_GetLenth(L);
//    cout << num << endl;
//    //cout << L->next->next->data<<endl;
//    return 0;
// }  
#define Next 0
#define Prior 1
int main()
{
   DLinklist D;
   D = DLinklist_Init(D);
   // cout << D->next->data <<endl;
   // cout << D->prior->data <<endl;
   DLinklist_Insert(D,5,Prior);
   DLinklist_All_Out(D);
   return 0;
}

易错整理:

一、链表总的首元结点、头结点、头指针的区别

三者的基本概念:

1、首元结点:就是指链表中存储第一个数据元素a1的结点。

2、头结点:它是在首元结点之前设的一个节点,其指针域指向首元结点。头

结点的数据域可以不存储信息,也可以存储与数据元素类型的其他附加信息。

3、头指针:它是指向链表中的第一个结点的指针。若链表设有头结点,则头

指针所指结点为线性表的头结点;若链表不设头结点,则头指针所指结点为该线

性表的首元结点。

4、尾指针:指向链表中的终端节点的指针。

链表增加头结点的作用有以下几点:

在这里插入图片描述
1、增加了头结点后,首元结点的地址保存在头结点(就是所说的“前驱”结点)的

指针域中,则对链表的第一个数据元素的操作与其他数据元素相同,无需进行特

殊处理

2、便于空表的和非空表的统一处理;当链表不设头结点时,假设L为单链表的头

指针,它应该指向首元结点,则当单链表为长度n为0的空表时,L指针为空(判断

空表的条件可记为:L==NULL)

3、增加头结点后,无论链表是否为空,头指针都是指向头结点的非空指针,若链

表为空的话,那么头结点的指针域为空。

二、单循环链表的尾指针

在单循环链表中,一般使用尾指针而非头指针。

尾指针是指向终端结点的指针,用它来表示单循环链表可以使得查找链表的开始

结点和终端结点都很方便。

设一带头结点的单循环链表,其尾指针为rear,则开始结点和终端结点的位置分别

是rear->next->next和rear,查找时间都是O(1)。 若用头指针来表示该链表,则查找

终端结点的时间为O(n)。

三、关于删除链表最后一个元素

因为删除链表最后一个元素需要访问到终端结点的前驱结点,让该节点指向

NULL,再将终端结点free掉,因此应该采用双链表便于访问前驱结点。

其实删除首元素也是同理的应灵活分析,关键在于,进行删除或者插入操作,需

要先访问哪个节点,用哪种链表最容易访问到。
在这里插入图片描述
在这里插入图片描述

四、关于引用传参和指针传参

先看一下插入结点的这个操作:

void LinkList_Insert(LinkList L,int i) //传入指针
或者
void LinkList_Insert(LinkList &L,int i) //引用操作
{
    LinkList p,q;
    int x;
    p = LinkList_Search(L,i);
    q = new(LNode); //给形参分配内存
    cin >> x ;
    q->data = x;
    q->next = p->next;
    p->next = q;
}

这种情况下,这两种操作其实都可以,没有本质区别。但是指针传参和引用有时

还是不同的,下面做一下辨析。

1、传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为

原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参

变量的操作就是对其相应的目标对象(在主调函数中)的操作。

2、使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接

对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参

分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝

构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效

率和所占空间都好。

3、使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被

调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行

运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点

处,必须用变量的地址作为实参。而引用更容易使用,更清晰。

4、如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数

中被改变,就应使用常引用。

const int &d = c;
//常引用 是让 变量引用 具有只读属性 不能通过d 去修改c,但改变c会让d的值变
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值