链表的几种写法(数据结构与算法课程1)
写在前面
因为这个学期刚好在上数据结构和算法这门课,就打算就此机会写一系列关于所有数据结构的总结和简单应用的博客,也加强自己对这些数据结构的理解。这篇文章主要总结了单链表的几种不同形式的写法,并且分别介绍了每种写法的特点,大家可根据实际情况选择适合自己的算法。
链表的简介
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。
使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。链表最明显的好处就是,常规数组排列关联项目的方式可能不同于这些数据项目在记忆体或磁盘上顺序,数据的存取往往要在不同的排列顺序中转换。链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。链表有很多种不同的类型:单向链表,双向链表以及循环链表。链表可以在多种编程语言中实现。像Lisp和Scheme这样的语言的内建数据类型中就包含了链表的存取和操作。程序语言或面向对象语言,如C,C++和Java依靠易变工具来生成链表。
链表的几种实现方式
1.结构体加指针的传统链表
传统链表的写法很好理解,我们每次只开一个地址,存储当前位置的值,和该值即将链接的下一个链表的地址,实际上每个值都在一个单独的地址。通过link指针相连。这种写法的好处就是很标准并且适合面向对象的应用。我们每定义一个新链表,只需要用一个变量保存表头的地址即可。
定义方法和基本操作
#include <iostream>
using namespace std;
class linklist
{
private:
struct node
{
int data;
node *link;
}*p;
public:
linklist();
void append( int num );
void add_as_first( int num );
void addafter( int c, int num );
void del( int num );
void display();
int count();
~linklist();
};
linklist::linklist()//定义新链表,p为表头
{
p=NULL;
}
void linklist::append(int num)//向后插入
{
node *res,n;
if(p==NULL)
{
p=new node;
p->data=num;
p->link=NULL;
}
else {
res=p;
while(res->link!=NULL)res=res->link;
n=new node;
n->data=num;
n->link=NULL;
res->link=n;
}
}
void linklist::add_as_first(int num)//向前插入
{
node *q;
q = new node;
q->data = num;
q->link = p;
p = q;
}
void linklist::addafter( int c, int num)//在第c个位置插入
{
node *q,*t;
int i;
for(i=0,q=p;i<c;i++)
{
q = q->link;
if( q == NULL )
{
cout<<"\nThere are less than "<<c<<" elements.";
return;
}
}
t = new node;
t->data = num;
t->link = q->link;
q->link = t;
}
void linklist::del( int num )//删除某个数据
{
node *q,*r;
q = p;
if( q->data == num )
{
p = q->link;
delete q;
return;
}
r = q;
while( q!=NULL )
{
if( q->data == num )
{
r->link = q->link;
delete q;
return;
}
r = q;
q = q->link;
}
cout<<"\nElement "<<num<<" not Found.";
}
void linklist::display()//展示整个链表
{
node *q;
cout<<endl;
for( q = p ; q != NULL ; q = q->link )
cout<<endl<<q->data;
}
int linklist::count()//统计节点个数
{
node *q;
int c=0;
for( q=p ; q != NULL ; q = q->link )
c++;
return c;
}
linklist::~linklist()//删除链表
{
node *q;
if( p == NULL )
return;
while( p != NULL )
{
q = p->link;
delete p;
p = q;
}
}
int main()
{
linklist ll;
cout<<"No. of elements = "<<ll.count();
ll.append(12);
ll.append(13);
ll.append(23);
ll.append(43);
ll.add_as_first(2);
ll.add_as_first(1);
ll.addafter(3,333);
ll.addafter(6,666);
ll.display();
cout<<"\nNo. of elements = "<<ll.count();
ll.del(333);
ll.del(12);
ll.del(98);
cout<<"\nNo. of elements = "<<ll.count();
return 0;
}
基于STL实现的链表LIST
提到数据结构,我们很难不提到STL这一工具,STL库中封装好的链表无论是适应性还是复杂度,都是非常优秀的一种写法,这里介绍的链表list实质上是一个双向链表,我们可以用它的一些内置函数高效的完成链表的操作。下面分步介绍一下这种方法的链表应用。
#include<list>
list<int > list1; // 声明一个空的list1
list<int> list2 (4, 100); // 声明一个含4个100的list2
list<int> list3 (list2.begin(), list2.end()); // 用list2的迭代器内容声明list3
list<int> list4 (list3); // 声明list4为list3的一个副本
list<int> list5(size); // 声明一个含size个默认值为0的list5
list1.front();//返回第一个元素的值。
list1.back();//返回最后一个元素的值。
lint4.empty()
list1.push_front(x);//把元素x插入到链表头部。
list1.pop_front();//删除第一个元素。
list1.push_back(x);//把元素x插入到链表尾部。
list1.pop_back();//删除最后一个元素。
list1.remove(val);//删除链表中所有值为val的元素。
lint1.erase(lint1.begin()++);//在list任意位置(迭代器位置参数)删除one数据
list1.unique();//删除链表中所有重复的元素
sort();//根据默认的谓词对链表排序,默认升序。
//正向遍历
for (list<int>::iterator it = lint1.begin(); it != lint1.end(); ++it)
cout << *it << " ";
cout << endl;
//反向遍历
for (auto it = lint1.rbegin(); it != lint1.rend(); ++it)
cout << *it << " ";
cout << endl;
数组模拟指针链表
用数组模拟链表的本质就是用一个新数组存储下一个数据存储的位置,也就是说原数组虽然可能地址连续,但是链表的链式关系是通过next数组的数值来索引的。我们可以分开写也可以写到同一个结构体里
结构体写法
#include<iostream>
using namespace std;
struct list{
int data;
int next;
}a[15];
int main()
{
int n, x;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>x;
a[i].data=x;
a[i].next=i+1;
}
a[n].next=0;
for(int i=1 ;i ;i=a[i].next)cout<<a[i].data<<" ";
cout<<endl;
//删除第三个数值
a[2].next=a[3].next;
for(int i=1 ;i ;i=a[i].next)cout<<a[i].data<<" ";
cout<<endl;
// 在第4个元素后插入100后输出链表
a[n+1].data=100;
a[n+1].next=a[4].next;
a[4].next=n+1;
for(int i=1 ;i ;i=a[i].next)cout<<a[i].data<<" ";
return 0;
}
标准Next数组写法
const int N = 100010;
int e[N], next[N], head, idx;//head记录表头,idx记录链表结尾
void init() //初始化链表
{
head = -1;
idx = 0;
}
void add_to_head(int x) //加入元素到表头
{
e[idx] = x; //先记录输入节点的权值
next[idx] = head; //让输入节点的指针指向head指向的节点
head = idx ++; //让head节点指向该节点,同时记录的下标加一
}
void add(int k, int x) //加入元素到任意节点后
{
e[idx] = x;
next[idx] = next[k]; //这里的理解和上面差不多,把head换成了ne[k]而已
next[k] = idx ++;
}
void remove(int k) //删除,注意我们删除的是第k + 1个节点
{
if(k == 0) head = next[head];
else next[k] = next[next[k]];
}
//这里还要有一点就是链表的遍历,输出或查询链表的时候会用到
void ptinrt()
{
for(int i = head; i != -1; i = next[i])
printf("%d ", e[i]);
printf("\n");
}
单链表的几种简单写法大致也总结完成了,对于双链表或一些其他问题,我们都可以按上面的思想,举一反三,有兴趣的可以尝试自己实现。

浙公网安备 33010602011771号