【数据结构 C++】线性表(二)链式存储

链式存储与顺序存储有一些不同的地方,而且比顺序存储要难。

在了解链式存储之前,我们先回顾一下指针。

一、指针

1、什么是指针

指针:是一个用来存放地址的变量,其值是指向另一个变量的地址。
所以说我认为完全可以把它当成普通的变量,只是普通的变量里存放的是数据如:int i=5,而指针里存放的就是一个地址,这个地址就能指向一个变量。

2、指针变量

1)、定义指针变量:int *p;

注意:这里的p是变量,就像int i中的i一样,而int *代表就是p是int 型的指针变量,就像i是int型的变量一样。
注意:指针变量一定要进行初始化,如int *p=NULL。否则p是野指针,会报错。

2)、程序中的*p(除定义外)

p指的的一个变量的地址,那么*p就是其存储的地址所指向的变量
示例如下:

#include <iostream>

using namespace std;

int main()
{
    int a=5;
    int *p=&a;
    cout<<"&a="<<&a<<endl;
    cout<<"p="<<p<<endl;
    cout<<"a="<<a<<endl;
    cout<<"*p="<<*p<<endl;
    return 0;
}

输出:
在这里插入图片描述

3、指针与数组

1)、指针与数组名

我们先看下面这段代码:

#include <iostream>

using namespace std;

int main()
{
    int *p;
    int a[5]={0,1,2,3,4};
    p=a;
    for(int i=0;i<5;i++)
    {
        cout<<p[i]<<" ";
    }
    cout<<endl;
    return 0;
}

输出结果为:
在这里插入图片描述
可见完全可以用p代表a,这是为什么呢?
因为a既是数组名也是指向整个元素的首地址,因此用p指向a,即可以用p访问数组a;
但是我想说:p指针是指针变量,而数组名a只是一个指针常量。

2)、指针数组

指针数组的本质是数组,数组中每一个成员是一个指针
如int *Array[5];
即代表:
在这里插入图片描述

二、链表

1、链表定义

链式存储和顺序存储的不同在于顺序存储用的是连续的地址,但是链式存储是用一组任意的存储单元存储数据的,地址可以是连续的也可以是不连续的。
为了能表示(存储)元素之间的逻辑关系(线性),在存放每个元素的同时,也存放相关元素的信息(相关元素的存储地址),即用指针来表示元素之间的逻辑关系。存放一个数据元素占用的空间为:
在这里插入图片描述

2、头结点

有时为了操作方便,在链表的第一个结点之前加一个“头结点”,该结点不存放元素,其指针指向线性表的第一个元素。
有了头结点后,对在第一个元素结点前插入结点和删除第一个结点,其操作与对其它结点的操作统一了。(不是必须的)

3、链表实现

我们用两个类来实现对链表的定义以及操作,一个是链表节点类LinkNode,用来定义一些指针节点以及初始化和分配内存;另一个是链表类,用来定义一些对于链表的操作。(链表类是链表节点类的友元)

1)、定义节点类

typedef int T;
class Linklist;
class LinkNode
{
   friend class LinkList;//声名友元
private:
   LinkNode *link;
   T data;
public:
   LinkNode(LinkNode *ptr=NULL)
   {
     link=ptr;
   }
   LinkNode(const T &item,LinkNode *ptr=NULL)//item是默认参数
   {
      data=item;
      link=ptr;
   }
   ~LinkNode(){};
};

注:默认参数必须是函数参数列表中最右边的参数,在函数调用的时候可以省略,当调用该函数时若实参的个数和定义时形参的个数不一致时,默认是从左往右匹配,所以默认参数要放在函数列表的最右边。在调用时,入股不写参数,则为第一个,如果写了一个T 型数据,则为第二个

2)、定义链表类

class LinkList
{
private:
    LinkNode *first;
public:
    LinkList();//构造函数
    ~LinkList();
    int Length();//返回链表的长度
    bool Insert(int i,T x);//向链表中插入元素
    LinkNode *Locate(int i);//返回链表i处的节点值
    void Input(T endTag);//按条件输入(顺序)
    void Input2(T endTag);//按条件输入(逆序)
    void PrintList();//打印链表
    bool Delete(T &x);//删除值为x的节点
    void Search(T &x);//按值查找
    bool Find(T x);//查询链表中是否存在x
    void Cross(LinkList &A,LinkList &B,LinkList &C);//链表的交操作
    void Union(LinkList &A,LinkList &B);//链表的并操作
    void Sort();//对链表进行排序
};

3)、构造函数

LinkList::LinkList()
{
    first = new LinkNode;//定义一个头结点并进行初始化
    first->data = 0;
    first->link = NULL;
}

4)、析构函数

LinkList::~LinkList()
{
    delete first;//删除头结点
}

5)、返回链表的长度

由于first是我们定义的头结点,所以我们应该将p设置为first的下一个结点,从这里开始计算长度。

int LinkList::Length()
{
    int i=0;
    LinkNode *p=first->link;
    while(p!=NULL)
    {
        i++;
        p=p->link;
    }
    return i;
}

6)、返回链表i处的节点值

LinkNode *LinkList::Locate(int i)
{
    LinkNode *p=first;
    int j=0;
    if(i<0)
    {
        return NULL;
    }
    while(p!=NULL&&j<i)
    {
        p=p->link;
        j++;
    }
    return p;
}

7)、向链表中插入元素

将p置于所要插入的结点的前一个位置,定义一个data值为x的新结点s,将s的下一个结点也指向p的下一个结点,再将p的下一个结点指向s

bool LinkList::Insert(int i,T x)
{
    LinkNode *p=Locate(i-1);
    if(p==NULL)
    {
        return false;
    }
    LinkNode *s=new LinkNode(x);
    s->link=p->link;
    p->link=s;
}

在这里插入图片描述
下面对部分操作进行分析:
s->link=p->link:将s指向的下一个结点变成p指向的下一个结点
在这里插入图片描述
p->link=s:将p的下一个结点指向s
在这里插入图片描述

8)、按条件输入(顺序)

新元素总是链接在后面,元素按自然顺序输入

void LinkList::Input(T endTag)
{
    LinkNode *newNode,*r;
    T val;
    cin>>val;
    r=first;
    while(val!=endTag)
    {
        newNode=new LinkNode(val);//申请空间,存储读入的元素
        r->link=newNode;
        r=newNode;
        cin>>val;
    }
    r->link=NULL;
}

在这里插入图片描述
在这里插入图片描述

9)、按条件输入(逆序)

新元素总是链接在前面,按相反的顺序输入
这个和插入很像

void LinkList::Input2(T endTag)
{
    LinkNode *newNode,*r;
    T val;
    cin>>val;
    r=first;
    while(val!=endTag)
    {
        newNode=new LinkNode(val);
        newNode->link=r->link;
        r->link=newNode;
        cin>>val;
    }
}

10)、打印链表

void LinkList::PrintList()
{
    LinkNode *p=first->link;
    while(p!=NULL)
    {
        cout<<p->data<<" ";
        p=p->link;
    }
    cout<<endl;
}

11)、删除值为x的节点

算法基本思想:找到第i-1个元素,使第i+1个元素成为其后继。释放第i个元素。

bool LinkList::Delete(T &x)
{
    LinkNode * p, *q = NULL;
    p = first->link;
    if (p == NULL)
    {
        return false;
    }
    while (p->data!=x&&p->link!=NULL)
    {
        q = p;//找到第i-1个元素,并赋给q
        p = p->link;//让p到第i个元素
    }
    if (p->data == x)
    {
        q->link = p->link;//让q的下一个元素直接指向p的下一个元素
        delete p;
    }
}

在这里插入图片描述

12)、按值查找

void LinkList::Search(T &x)
{
    LinkNode *p=first->link;
    int count=1,flag=0;
    while(p!=NULL)
    {
        if(p->data==x)
        {
            flag=1;
            break;
        }
        count++;
        p=p->link;
    }
    if(flag)
    {
        cout<<x<<" is located at index of "<<count<<endl;
    }
    else
    {
        cout<<x<<" is not found"<<endl;
    }
}

13)、查询链表中是否存在x

bool LinkList::Find(T x)
{
    LinkNode *p=first;
    while(p!=NULL)
    {
        if(p->data==x)
        {
            return true;
        }
        p=p->link;
    }
    return false;
}

14)、链表的交操作

在链表B中遍历,将值取出在链表A中寻找,如果找到,就在链表C中添加

void LinkList::Cross(LinkList &A,LinkList &B,LinkList &C)
{
    T t;
    for(int i=1; i<=B.Length(); i++)
    {
        t=B.Locate(i)->data;
        if(A.Find(t))
        {
            C.Insert(C.Length()+1,t);
        }
    }
}

15)、链表的并操作

在链表B中遍历,将值取出在链表A中寻找,如果没找到,就在链表A尾部添加

void LinkList::Union(LinkList &A,LinkList &B)
{
    T t;
    for(int i=1; i<=B.Length(); i++)
    {
        t=B.Locate(i)->data;
        if(!A.Find(t))
        {
            A.Insert(A.Length()+1,t);
        }
    }
}

16)、对链表进行排序

类似于冒泡排序

void LinkList::Sort()
{
    LinkNode *p=first,*q=NULL;
    while(p!=q)
    {
        while(p->link!=q)
        {
            if(p->data>p->link->data)
            {
                int t=p->data;
                p->data=p->link->data;
                p->link->data=t;
            }
            //如果没有找到满足if条件的,就一直往后遍历
            //如果找到了,进行交换后再往后遍历
            //一直到p->link!=q
            p=p->link;
        }
        //将q指向p,在将p置为first头结点,从头再进行遍历操作
        q=p;
        p=first;
    }
}

4、完整程序

#include <iostream>
#include <stdio.h>

using namespace std;

typedef int T;

class LinkList;
class LinkNode
{
    friend class LinkList;
private:
    LinkNode *link;
    T data;
public:
    LinkNode(LinkNode *ptr=NULL)
    {
        link=ptr;
    }
    LinkNode(const T &item,LinkNode *ptr=NULL)
    {
        data=item;
        link=ptr;
    }
    ~LinkNode() {};
};

class LinkList
{
private:
    LinkNode *first;
public:
    LinkList();//构造函数
    ~LinkList();
    int Length();//返回链表的长度
    bool Insert(int i,T x);//向链表中插入元素
    LinkNode *Locate(int i);//返回链表i出的节点值
    void Input(T endTag);//按条件输入(顺序)
    void Input2(T endTag);//按条件输入(逆序)
    void PrintList();//打印链表
    bool Delete(T &x);//删除值为x的节点
    void Search(T &x);//按值查找
    bool Find(T x);//查询链表中是否存在x
    void Cross(LinkList &A,LinkList &B,LinkList &C);//链表的交操作
    void Union(LinkList &A,LinkList &B);//链表的并操作
    void Sort();//对链表进行排序
};

LinkList::LinkList()
{
    first = new LinkNode;
    first->data = 0;
    first->link = NULL;
}


LinkNode *LinkList::Locate(int i)
{
    LinkNode *p=first;
    int j=0;
    if(i<0)
    {
        return NULL;
    }
    while(p!=NULL&&j<i)
    {
        p=p->link;
        j++;
    }
    return p;
}

LinkList::~LinkList()
{
    delete first;
}

bool LinkList::Insert(int i,T x)
{
    LinkNode *p=Locate(i-1);
    if(p==NULL)
    {
        return false;
    }
    LinkNode *newNode=new LinkNode(x);
    newNode->link=p->link;
    p->link=newNode;
}

void LinkList::Input(T endTag)
{
    LinkNode *newNode,*r;
    T val;
    cin>>val;
    r=first;
    while(val!=endTag)
    {
        newNode=new LinkNode(val);
        r->link=newNode;
        r=newNode;
        cin>>val;
    }
    r->link=NULL;
}

void LinkList::Input2(T endTag)
{
    LinkNode *newNode,*r;
    T val;
    cin>>val;
    r=first;
    while(val!=endTag)
    {
        newNode=new LinkNode(val);
        newNode->link=r->link;
        r->link=newNode;
        cin>>val;
    }
}

void LinkList::PrintList()
{
    LinkNode *p=first->link;
    while(p!=NULL)
    {
        cout<<p->data<<" ";
        p=p->link;
    }
    cout<<endl;
}

bool LinkList::Delete(T &x)
{
    LinkNode * p, *q = NULL;
    p = first->link;
    if (p == NULL)
    {
        return false;
    }
    while (p->data!=x&&p->link!=NULL)
    {
        q = p;
        p = p->link;
    }
    if (p->data == x)
    {
        q->link = p->link;
        delete p;
    }

}

int LinkList::Length()
{
    int i=0;
    LinkNode *p=first->link;
    while(p!=NULL)
    {
        i++;
        p=p->link;
    }
    return i;
}

void LinkList::Search(T &x)
{
    LinkNode *p=first->link;
    int count=1,flag=0;
    while(p!=NULL)
    {
        if(p->data==x)
        {
            flag=1;
            break;
        }
        count++;
        p=p->link;
    }
    if(flag)
    {
        cout<<x<<" is located at index of "<<count<<endl;
    }
    else
    {
        cout<<x<<" is not found"<<endl;
    }
}

bool LinkList::Find(T x)
{
    LinkNode *p=first;
    while(p!=NULL)
    {
        if(p->data==x)
        {
            return true;
        }
        p=p->link;
    }
    return false;
}

void LinkList::Cross(LinkList &A,LinkList &B,LinkList &C)
{
    T t;
    for(int i=1; i<=B.Length(); i++)
    {
        t=B.Locate(i)->data;
        if(A.Find(t))
        {
            C.Insert(C.Length()+1,t);
        }
    }
}

void LinkList::Union(LinkList &A,LinkList &B)
{
    T t;
    for(int i=1; i<=B.Length(); i++)
    {
        t=B.Locate(i)->data;
        if(!A.Find(t))
        {
            A.Insert(A.Length()+1,t);
        }
    }
}

void LinkList::Sort()
{
    LinkNode *p=first,*q=NULL;
    while(p!=q)
    {
        while(p->link!=q)
        {
            if(p->data>p->link->data)
            {
                int t=p->data;
                p->data=p->link->data;
                p->link->data=t;
            }
            p=p->link;
        }
        q=p;
        p=first;
    }
}

int main()
{
    int n,m,t=0,a[10000],x1,y1,x2,x3,x4;
    cin>>n;
    LinkList p,q,r;
    p.Input(n);
    cout<<"A is created as: ";
    p.PrintList();
    cin>>x1>>y1;
    p.Insert(x1,y1);
    cout<<"After inserted A is ";
    p.PrintList();
    cin>>x2;
    p.Delete(x2);
    cout<<"After deleted A is ";
    p.PrintList();
    cin>>x3;
    p.Search(x3);
    cin>>x4;
    p.Search(x4);
    cin>>m;
    q.Input(m);
    cout<<"B is created as: ";
    q.PrintList();
    p.Cross(p,q,r);
    cout<<"A cross B is ";
    r.PrintList();
    p.Union(p,q);
    cout<<"A union B is ";
    p.PrintList();
    p.Sort();
    cout<<"A union B in sequence is ";
    p.PrintList();
    return 0;
}

5、说明

1)、每次用结点类定义结点指针后一定要进行初始化

2)、链表尽量定义一个头结点,方便操作,但是头结点不属于整个链表。

在这里插入图片描述

3)、关于定义的节点初始化分配内存

如果我们想对插入某一个数时,先用结点类的构造函数对其初始化
LinkNode *newNode=new LinkNode(val),这样可以为该数申请空间,分配内存。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值