链表的引入,查找慢是线性表的通病,链表练习:自行车停放--摘抄

萌新: 数组真不错啊,使用连续的存储空间,访问和使用都很简单。

大佬: 是的,不过数组不够灵活,它有以下缺点:

  • 需要占用连续空间。若某个数组很大,可能没有这么大的连续空间给它用。
  • 不方便删除和增加。若删除了数组中间一段数据,就需要把后面的往前挪,产生大量的拷贝开销。若需要增加空间,也不方便操作。

萌新: 那遇到了这类问题该怎么办呀?

大佬: 那就要引入我们这节课的主角——链表了。链表不需要把数据存储在连续的空间上,而且删除和增加空间都很方便。链表可以看成是用指针串起来的数组,它是用一组位于任意位置的存储单元存线性表的数据元素,这组存储单元可以是连续的,也可以不连续。 链表又可分为单向链表和双向链表:

  • 单向链表如下图。单向链表一般首尾相接,最后的nextnext指向第一个data。

双向链表见下图。双向链表一般首尾相接,最后的next指针指向第一个data,第一个pre指针指向最后的data。  

链表的引入,查找慢是线性表的通病,链表练习:自行车停放--摘抄_第1张图片

双向链表比单向链表的访问稍微方便一点点,也快一点点。在需要频繁访问前后几个结点的场合,可以使用双向链表。

链表这种数据结构用起来比较简单,它的操作有初始化、添加、遍历、插入、删除、查找、排序、释放等。

萌新: 哇,链表这么NB,那还要数组作甚?

大佬: 链表也是有缺点的,它的查找元素比较慢,复杂度为O(n)。比如需要查找data等于某个值的结点时,可能要遍历整个链表,才能找到它。查找慢是线性表的通病

萌新: 原来是这样,那么链表该如何实现呢?

大佬: 链表的实现有动态链表、静态链表、STL链表等多种方法。在算法竞赛中为加快编码速度,一般用静态链表或STL list。一会我会重点讲讲静态链表和 STL list 的实现。

  • 静态链表

静态链表使用预先分配的一段连续空间来存储链表。下面给出用结构体数组实现的单向静态链表。

const int N = 10000;                   //按需要定义静态链表的空间大小
struct node{                           //单向链表
    int id;                            //这个结点的id
    int data;                          //数据
    int nextid;                        //指向下一个结点的id
}nodes[N];                             //静态分配需要定义在全局

//为链表的next指针赋初值,例如:
    nodes[0].nextid = 1;
    for(int i = 1; i <= n; i++){
        nodes[i].id = i;     //把第i个结点的id就赋值为i
        nodes[i].nextid = i + 1;   //next指针指向下一个结点
    }

//定义为循环链表:尾指向头
    nodes[n].nextid = 1;                 

//遍历链表,沿着nextid访问结点即可

//删除结点。设当前位于位置now,删除这个结点  
    nodes[prev].nextid = nodes[now].nextid;   //跳过结点now,即删除now
    now = nodes[prev].nextid;                 //更新now

链表的引入,查找慢是线性表的通病,链表练习:自行车停放--摘抄_第2张图片

  • 双向静态链表

下面是用结构体数组实现的双向静态链表

const int N = 10000;
struct node{                          //双向链表
    int id;                           //结点编号
    int data;                         //数据
    int preid;                        //前一个结点
    int nextid;                       //后一个结点
}nodes[N];

//为结点的指针赋初值,例如
    nodes[0].nextid = 1;  
    nodes[1].preid  = 0;
    for(int i = 1; i <= n; i++){       //建立链表
        nodes[i].id = i;
        nodes[i].preid  = i-1;          //前结点
        nodes[i].nextid = i+1;          //后结点
    }
//定义为循环链表
    nodes[n].nextid = 1;               //循环链表:尾指向头
    nodes[1].preid = n;                //循环链表:头指向尾

//遍历链表,沿着preid和nextid访问结点即可

//删除结点。设当前位于位置now,删除这个结点
    prev = nodes[now].preid;   
    next = nodes[now].nextid;
    nodes[prev].nextid = nodes[now].nextid;  //删除now
    nodes[next].preid  = nodes[now].preid;   
    now = next;                              //更新now

//插入结点,见后面的习题“自行车停放”

链表的引入,查找慢是线性表的通病,链表练习:自行车停放--摘抄_第3张图片

  • STL-list

上述链表的手写代码已经比较简单了,如果还嫌麻烦,可以使用C++的STL list。 list 是双向链表,它的内存空间可以是不连续的,通过指针访问结点数据,它能高效率地删除和插入。

//定义一个list
    listnode;
//为链表赋值,例如定义一个包括n个结点的链表
    for(int i=1;i<=n;i++)  
        node.push_back(i); 
//遍历链表,用it遍历链表,例如从头遍历到尾:
    list::iterator it = node.begin();
    while(node.size()>1){           //list的大小由STL自己管理
         it++;
         if(it == node.end())   //循环链表,end()是list末端下一位置
                it = node.begin();                                              
    }
//删除一个结点
    list::iterator next = ++it;
    if(next==node.end())  next=node.begin();  //循环链表
    node.erase(--it);            //删除这个结点,node.size()自动减1
    it = next;                   //更新it

//插入结点,见后面的习题“自行车停放”

链表的引入,查找慢是线性表的通病,链表练习:自行车停放--摘抄_第4张图片

链表练习:自行车停放

链表的引入,查找慢是线性表的通病,链表练习:自行车停放--摘抄_第5张图片

#include 
using namespace std;
const int N = 200010;
struct node{                          //双向链表
    //int id;                         //结点编号,没用到
    int data;                         //数据
    int preid;                        //前一个结点
    int nextid;                       //后一个结点
}nodes[N];
int now;
int locate[N];    //locate(x) = now; 值为x的结点位置在nodes[now]
void init() {
    nodes[0].nextid = 1;
    nodes[1].preid  = 0;
    now = 2;
}
void insert(int k, int x) {  //插入一个nodes[now],插到nodes[k]的右面
    nodes[now].data = x;
    locate[x] = now;         //记录值为x的结点的位置
    nodes[now].nextid = nodes[k].nextid;
    nodes[now].preid = k;
    nodes[nodes[k].nextid].preid = now;
    nodes[k].nextid = now;
    now++;
}
int main() {
    int n;     cin >> n;
    init();
    int a;     cin >> a;    //第一辆车编号
    insert(0, a);
    n--;
    while (n--) {
        int x, y, z;   cin >> x >> y >> z;
        if (z == 0)    //把x插到y的左边
            insert(nodes[locate[y]].preid, x); //用locate[]快速定位
        else           //把x插到y的右边
            insert(locate[y], x);
    }
    for (int i = nodes[0].nextid; i != 1; i = nodes[i].nextid)
        cout << nodes[i].data << " ";

    return 0;
}

链表的引入,查找慢是线性表的通病,链表练习:自行车停放--摘抄_第6张图片

链表的引入,查找慢是线性表的通病,链表练习:自行车停放--摘抄_第7张图片

  • STL list

大佬:当然你也使用 STL list 求解,它的代码量也少了不少;注意我代码中 loc的作用,它和上面代码中 locate[]的作用一样。

萌新: C++的STL真香。

#include 
using namespace std;

long long n,x,a,b,c;
list::iterator loc[100003];    //小技巧

int main(){
   long long i;
   list L;    //链表
   scanf("%ld %ld",&n,&x);
   L.push_back(x);         //插入刚开始编号
   loc[x] = L.begin();     //迭代器地址存入数组
   list::iterator temp; //临时迭代器
   for(i=1;i<=n-1;i++){
        cin>>a>>b>>c; //a为待插元素编号  b为表中元素编号  c表示左右
        temp = loc[b];
        if(c==0){
            L.insert(temp,a);
                   //L.insert函数表示在链表L的temp位置前插入元素a
            loc[a] = --temp; //将新插入的元素地址记录到数组中
}
       else{
            L.insert(++temp,a);
            loc[a] = --temp;
        }
}
    for(list::iterator it=L.begin();it!=L.end();it++)
    cout<<*it<<" ";
    return 0;
}
  • Java

本题的Java代码,请参考 蓝桥杯 算法训练 自行车停放_sunnnnh的博客-CSDN博客

你可能感兴趣的:(链表,数据结构)