数据结构(链表)

链表及其实现

链式结构

顺序表插入、删除时间代价的分析,可以看出其时间复杂度是线性阶的,而且会引起大量已存储元素的位置移动。

改进方法:链式结构

Ø各个元素的物理存放位置在存储器中是任意的,不一定连续。
Ø每个元素放在一个独立的存储单元中,元素间的逻辑关系依靠存储单元中附加指针来完成。
Ø采用链式存储结构存储的线性表,称为链表

链表的存储映像图

 

 为了清晰看出逻辑关系,以后链表用图(b)来表示。

 结构类型、结构变量、结构指针

#结构体
struct dateT
{   int year;
    int month;
    int day;
};
#类
class dateT
{  public:
      int year;
      int month;
      int day;
};
#结构变量、结构指针
dateT  d, *p;
d.year = 2020;
d.month = 3;
d.day = 11;
p = &d;
cout<<p->year<<endl;
cout<<p->month<<endl;
cout<<p->day<<endl;

dateT  d, *p;
p = new dataT;
p->year = 1010;
p->month = 1;
d->day = 1;
delete p; 
p->year = 2021; //非法
p = &d;  //合法

  *p在此是指针,在后面delete p,系统回收的是无名氏对应的内存,而不是指针,故可以在后面重新将指针指向没被回收的d内存,p=&d表示指针p指向内存d的起始地址。指针只会在程序结束时由系统自动回收。

结点和链表

 单链表的特点

Ø头指针head指向了头结点。
Ø头结点并不是线性表中的一部分,它的指针字段next给出了首结点的地址。
Ø线性表中最后一个结点的指针字段next的值为NULL。
Ø顺着头指针head,可以很方便地逐个访问单链表中的所有结点。

单链表类

Ø它的任何一个结点包含了一个存储元素数据值的字段和一个存储该结点的直接后继结点地址的指针字段。
Ø提供一个单链表只需要给出头结点的地址即头指针。
Ø单链表结点类定义、单链表类定义

单链表结点类(linkList.h) 

class outOfBound{};
template <class elemType>
class linkList; //类的前向说明

template <class elemType>
class node
{   friend class linkList<elemType>;
     private:
        elemType data;
        node *next;
 public:
        node():next(NULL){};
        node(const elemType &e, node *N=NULL)
        {  data = e; next = N; };
};

单链表类

template <class elemType>
class linkList
{   private:
        node<elemType> *head;
    public:
        linkList();  //构造函数,建立一个空表
        bool isEmpty ()const; //表为空返回true,否则返回false。
        bool isFull ()const {return false;}; //表为满返回true,否则返回false。
        int length ()const;  //表的长度
        elemType get(int i)const;//返回第i个元素的值
        //返回值等于e的元素的序号,从第1个开始,无则返回0.
        int find (const elemType &e )const;
        //在第i个位置上插入新的元素(值为e)。
        void insert (int i, const elemType &e );
        //若第i个元素存在,删除并将其值放入e指向的空间。
        void remove (int i, elemType &e);
        void reverse()const; //元素就地逆置
        void clear (); //清空表,使其为空表
        ~linkList();
};

链表基本操作的实现代码(linkList.h)

template <class elemType>    //属性赋初值,模板函数用法
linkList<elemType>::linkList() //构造函数,建立一个空表
{
    head = new node<elemType>();
}

template <class elemType>
bool linkList<elemType>::isEmpty ()const //表为空返回true,否则返回false。
{
    if (head->next==NULL) return true;
    return false;
}

链表基本操作的实现分析

 

 

插入总结:遵循“先武装自己,再融入队伍”

1.在内存中创建新结点。

2.武装新结点:将x写入新结点的data字段,p指针所指结点的下一结点地址写入新结点的 next字段,使p所指结点的下一结点成为新结点的直接后继结点。

3.将新结点地址写入p的next字段,使新结点成为p所指结点的直接后继结点。

具体语句

#法一
tmp = new node<elemType>(); 
tmp->data = e;
tmp->next = p->next;
p->next = tmp;

#法二
tmp = new node<elemType>(e, p->next);
p->next = tmp;

#法三:四合一语句
p->next = new node<elemType>(e, p->next);

时间复杂度分析:

当P已经指向了插入位置的前一个结点时,插入操作和结点个数无关,时间复杂度为O(1)。

链表基本操作实现代码

template <class elemType>
void linkList<elemType>::insert (int i, const elemType &e )
//在第i个位置上插入新的元素(值为e)。
{   if (i<1) return;//参数i越界
    int j=0; node<elemType>  *p=head;
 
    while (p&&j<i-1)                      //注意此处一定要添加p这一条件,以便确保指针合法
    {  j++;  p=p->next; }
 
    if (!p) return; //参数i越界
    p->next = new node<elemType>(e, p->next);  
}

删除操作: 删除P指针所指结点之后的那个结点

删除总结:

1.记住待删除结点地址。
2.将值为x的结点旁路掉。
3.回收原本存储x的结点占用的空间。

具体语句:

node *q=p->next;
p->next = q->next;
delete q;

时间复杂度分析:

当P已经指向了待删除结点的前一个结点时,删除操作和结点个数无关,时间复杂度为O(1)。

查找操作:

  1. 找值为x的结点,顺首结点逐个向后检查、匹配。
  2. 单链表和顺序表中,时间复杂度都是O(n)。
  3. 找第k个结点,顺序表O(1),链表O(n)。

其他基本操作:

isFull:因每次只申请一个结点空间,故总为false。

clear:除了头结点删除并释放整个单链表中结点,回到初始化后的状态。

template <class elemType>
int linkList<elemType>::length ()const //表的长度
{
    int count=0;
    node<elemType> *p;
    p=head->next;
    while(p)
    {   count++;    p=p->next;  }
    return count;
}


template <class elemType>  //注意:五步口诀法
elemType linkList<elemType>::get(int i )const
//返回第i个元素的值,首元素为第1个元素
{
    if (i<1) throw outOfBound();
    int j=1;
    node<elemType> *p = head->next;
    while (p&&j<i) {p=p->next; j++;}
    if (p) return p->data;
    throw outOfBound();
}
 
template <class elemType>
int linkList<elemType>::find (const elemType &e )const
//返回值等于e的元素的序号,从第1个开始,无则返回0.
{   int i=1;
    node<elemType> *p = head->next;
 
    while (p)
    {   if (p->data==e) break;
        i++;   p=p->next;
    }
    if (p) return i;
    return 0;   }

两种常用技巧:兄弟协同法、首席插入法

兄弟协同法(使用两个指针,在此是指p指针和q指针)

template <class elemType>    //P、Q兄弟协同法
void linkList<elemType>::clear () //清空表,使其为空表
{    node<elemType> *p,*q;
    p=head->next;      head->next=NULL;
    while (p)
    {   q=p->next;
        delete p;
        p=q;
    }
}

 最快插入位置——“脖子”(首席插入法)

template <class elemType>
void linkList<elemType>:: insert(const elemType a[], int n)
{ node *tmp, 
   for (int i=0; i<n; i++)
   {     tmp = new node(a[i], head->next);   head->next = tmp;    }
}

a[5]={1,3,5,7,9}

练习

 练习描述:对一个单链表进行就地逆置---摆龙门阵

 

 

 

 

“兄弟协同法”+“首席插入法”实现单链表的就地逆置

template <class elemType>
void linkList<elemType>::reverse()
{   node<elemType> *p,*q;  //兄弟俩协同
    p=head->next;   head->next = NULL;  
    while (p)
    {  q = p->next;
       p->next = head->next; head->next = p; //首席插入
       p=q;
    }
}

常见错误

Ø指针p未被初始化或者为空,读取其指向的字段如p->data,如循环检查p所指的结点中值是否x,可用while (p && p->data!=x) p=p->next

Øp原本指向了一个结点,但其指向的结点空间已经释放,仍要读取其所指结点的字段。如p=head; delete p; p=p->next;

p->next非法访问了不能访问的内存空间

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

牛哥带你学代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值