线性表_单链表_常用操作接口和复杂度分析
- 链表的优点:
内存利用率高,不需要大块连续内存;
插入和删除不需要移动节点,时间复杂度为O(1);
不需要专门进行扩展操作,需要节点时候直接申请即可。 - 链表的缺点
内存占用量大,每一个节点多出存放地址的空间:每个节点多了一个指针,假如有100万整型数据,假设指针占4字节,那么100w整型需要400w字节;如果用链表实现,每个节点多出一个指针,存放100w整型数组需要800万字节;
链表每个节点都不连续,无法内存随机访问;
链表搜索效率不高,只能从头节点开始搜索。 - 数组和链表的应用对比
在通用情况下:如果下标访问/随机访问,那么用数组合适;但是数组的搜索还是O(n);
如果增加和删除频繁,用链表更合适O(1);链表的搜索也是O(n);
下面的代码用C++语法实现了链表接口的定义:
尾插,头插,打印链表,删除单个节点和所有重复节点,最后释放链表内存;
#include <iostream>
#include <stdlib.h>
#include <time.h>
using namespace std;
// 节点类型
struct Node
{
Node(int data = 0) :data_(data), next_(nullptr) {}
int data_;
Node *next_;
};
class Clink
{
public:
Clink()
{
// 给head_初始化指向头结点
head_ = new Node();
}
~Clink()
{
// 节点释放
Node *p = head_;
while (p != nullptr)
{
head_ = head_->next_;
delete p;
p = head_;
}
head_ = nullptr;
}
public:
// 尾插 O(n)
void InsertTail(int val)
{
Node *p = head_;
// 找到当前链表末尾节点
while (p->next_ != nullptr)
{
p = p->next_;
}
// 生成新节点
Node *node = new Node(val);
// 插入到尾部
p->next_ = node;
}
// 链表的头插 O(1)
void InsertHead(int val)
{
// 生成新节点
Node *node = new Node(val); // new = 开辟新节点 + 调用构造初始化新节点
// 新节点先指向头结点的下一个节点
node->next_ = head_->next_;
// 头节点指向新节点
head_->next_ = node;
}
// 打印链表
void Show()
{
Node *p = head_->next_; // 头结点没有存放数据,所以从第一个节点开始打印
while (p != nullptr) // 只有寻找尾结点时候,才p->next_ = nullptr 这样的用法
{ // 这样就会 漏掉 尾结点
cout << p->data_ << " ";
p = p->next_;
}
cout << endl;
}
// 删除所有值为val的节点
int Remove(int val)
{
// 先搜索头节点
Node *p = head_->next_;
Node *q = head_; // 记住跟屁虫
while (p != nullptr)
{
if (p->data_ == val)
{
q->next_ = p->next_;
delete p;
return 0;
//p = q->next_; // 删除后再让p继续指向q下一个,就可以删除重复节点
}
else
{
q = p;
p = p->next_;
}
}
cout << "未找到" << endl;
return -1;
}
void RemoveAll(int val)
{
Node *q = head_;
Node *p = head_->next_;
while (p != nullptr)
{
if (p->data_ == val)
{
q->next_ = p->next_;
delete p;
p = q->next_;
}
else
{
q = p;
p = p->next_;
}
}
return;
}
// 链表搜索 O(n) 注意和数组的下标访问区别。
bool Find(int val)
{
Node *p = head_->next_;
while (p != nullptr)
{
if (p->data_ == val)
{
return true;
}
else
{
p = p->next_;
}
}
return false;
}
private:
Node *head_;
// 可以增加一个新节点指向尾结点,降低尾插的时间复杂度
};
int main()
{
Clink link;
srand(time(0));
for (int i = 0; i < 10; i++)
{
int val = rand() % 100;
link.InsertHead(val);
cout << val << " ";
}
cout << endl;
link.Show();
link.InsertTail(250);
link.InsertTail(250);
link.Show();
link.RemoveAll(250);
link.Show();
system("pause");
return 0;
}