C++实现单链表

链表是一种以链式存储的线性表,由于内存空间可以不连续,使用十分灵活,与数组相比各有优缺点。

单链表需要有个标识指明起始位置以便于操作整个链表,可以是头指针也可以是头结点,只是实现上的不同,这里使用头结点的方式。

C++实现单链表_c++

头结点内存结构和数据节点相同,只是头结点不保存数据。有了头结点之后,要操作的第一个数据节点就是头结点的下一个节点。

struct Node
{
T data;
Node* next;
};

//通过定义与头结点相同内存布局的头节点变量,避免操作头结点数据,使用 &m_header 即获得指向头结点的指针
struct
{
char reserved[ sizeof(T) ];
Node* next;
} m_header;

主要实现接口:

position(int i)

节点指针定位

insert(int i, const T& e)

指定位置插入

insert(const T& e)

从尾部插入

remove(int i)

删除第i个节点

get(int i, T& e)

获取第i个节点

get (int i)

获取第i个节点const版本

traverse()

遍历整个链表

如何在链表任一位置插入节点

假设除了头结点之外,有两个数据节点,那么可插入位置为三个

C++实现单链表_插入节点_02

假设要在图中标识为0的位置插入节点,则插入位置是头结点的​​next​​节点(重要

同理在1的位置插入节点,则插入位置是头结点的​​next->next​​节点

同理在2的位置插入节点,则插入位置是头结点的​​next->next->next​​节点……

于是得出规律:

  • 在第0个位置插入,直接跟在头结点后
  • 在第1个位置插入,就从头结点跳过1个节点后插入
  • 在第2个位置插入,就从头结点跳过2个节点后插入……以此类推

节点指针定位

根据上一节的分析,在第i个位置插入节点,需要跳过i个节点,由此定位待插入位置的前一个节点

Node* position(int i)
{
Node* ret = reinterpret_cast<Node*>(&m_header);

for ( int p = 0; p < i; p++ )
{
ret = ret->next;
}

return ret;
}

插入函数

通过​​position​​函数得到目标位置的前一个位置后,就可以执行插入操作了,位置范围[0, m_length]

C++实现单链表_头结点_03

bool insert(int i, const T& e)
{
bool ret = ( 0 <= i && i <= m_length );

if( ret )
{
Node* node = new (std::nothrow) Node();
assert(nullptr != node);

Node* current = position(i);

node->data = e;
node->next = current->next;
current->next = node;

m_length++;
}
return ret;
}

//尾插重载版本
bool insert(const T& e)
{
return insert(m_length, e);
}

删除函数

同理删除节点时,也可以通过指针定位的方式,有点不同的是,删除时位置范围只有[0, m_length - 1]

C++实现单链表_插入节点_04

bool remove(int i)
{
bool ret = ( 0 <= i && i <m_length );

if (ret)
{
Node* current = position(i);
Node *toDel = current->next;

current->next = toDel->next;
m_length--;

delete toDel;
}

return ret;
}

访问第i个节点

T get (int i) const
{
T ret;

if ( get(i, ret) )
{
return ret;
}
else
{
throw std::out_of_range("get list element out of range ...");
}
}

bool get(int i, T& e)
{
bool ret = ( (0 <= i) && (i < m_length) );

if (ret)
{
e = position(i)->next->data;
}

return ret;
}

遍历整个链表

void traverse()
{
Node* current = position(1);
while( current )
{
std::cout << current->data << " ";
current = current->next;
}
std::cout << std::endl;
}
#include <iostream>
#include <cassert>

template<typename T>
class List
{
struct Node
{
T data;
Node* next;
};

struct
{
char reserved[ sizeof (T) ];
Node* next;
} m_header;

int m_length;

Node* position(int i)
{
Node* ret = reinterpret_cast<Node*>(&m_header);

for ( int p = 0; p < i; p++ )
{
ret = ret->next;
}

return ret;
}

public:
List()
{
m_header.next = nullptr;
m_length = 0;
}

bool insert(const T& e)
{
return insert(m_length, e);
}

bool insert(int i, const T& e)
{
bool ret = ( 0 <= i && i <= m_length );

if( ret )
{
Node* node = new (std::nothrow) Node();
assert(nullptr != node);

Node* current = position(i);

node->data = e;
node->next = current->next;
current->next = node;

m_length++;
}
return ret;
}

bool remove(int i)
{
bool ret = ( 0 <= i && i < m_length );

if (ret)
{
Node* current = position(i);
Node *toDel = current->next;

current->next = toDel->next;
m_length--;

delete toDel;
}

return ret;
}

T get (int i) const
{
T ret;

if ( get(i, ret) )
{
return ret;
}
else
{
throw std::out_of_range("get list element out of range ...");
}
}

bool get(int i, T& e)
{
bool ret = ( (0 <= i) && (i < m_length) );

if (ret)
{
e = position(i)->next->data;
}

return ret;
}

void traverse()
{
Node* current = position(1);
while( current )
{
std::cout << current->data << " ";
current = current->next;
}
std::cout << std::endl;
}

int length() const
{
return m_length;
}

void clear()
{
while (m_header.next)
{
Node* toDel = m_header.next;
m_header.next = toDel->next;
m_length--;
delete toDel;
}
}
};

int main()
{
List<int> list;

for ( int i = 0; i < 5; ++i )
{
list.insert(i);
}

list.traverse();//0 1 2 3 4

list.insert(3, 100);

list.traverse();//0 1 2 100 3 4

list.remove(0);
list.remove(2);

list.traverse();//1 2 3 4

list.clear();

list.traverse();//空

return 0;
}
//运行结果
0 1 2 3 4
0 1 2 100 3 4
1 2 3 4

参考

  • 狄泰软件学院:数据结构实战开发