链表的特点:链表与顺序表有着极大的不同,顺序存储通常以数组的形式开始定义,而定义是必须要规定数组的大小,如果在后期需要改动,会带来极大的不方便。而链式存储的长度可以动态的申请,用来解决顺序存储中存在的存储空间难以确定的问题。
其中有关于链表有以下几个概念:
结点node node: 结点由数据域和指针域组成, 数据域中可以是任意类型的数据包括结构体甚至其他数据结构, 指针域中是指向链表下一个结点的指针。
头节点 head node:头结点也称表头 哑结点, 数据域无意义, 指针域存储指向链表第一结点的指针, 并将其看作指向当前链表的指针, 注意约定头结点是链表的第0个结点, 而非第1个。
头指针 head pointer: 指向链表头结点的指针, 无头结点时指向链表第一个结点.
位置 position: 一个结点在链表中占据一个位置, 头结点的位置为0。
表长 list length:表中结点的个数(除头结点) 。
前驱 previous node: 当前结点的前一个结点.。(双链表中使用)
后继 next node: 当前结点的下一个结点。
空表 empty list: 只有头结点的的链表, 表长为0。
链表的结构如下图:
利用模板来定义一个结点:
template<class T>
struct Node
{
T data; // 存储的数据
Node<T>* next; // 后继节点
};
下面进行单链表的实现,以下有三个构造函数,分别为无参,头插法,尾插法三种。
无参构造函数
Linklist()
{
first = new Node<T>;
first->next = NULL; // 链表为空,头结点指向NULL
}
头插法构造函数
Linklist(T a[], int n) // 头插法
{
first = new Node<T>;
first->next = NULL;
Node<T>* s;
for (int i = 0; i < n; ++i)
{
s = new Node<T>;
s->data = a[i];
s->next = first->next; // 新插入的s结点的后继为原先first结点的后继
first->next = s; // first结点的后继更新为s
}
}
头插法的示意图:
尾插法构造函数
Linklist(int n, T a[]) // 尾插法
{
first = new Node<T>;
Node<T>* s;
Node<T>* r; // 为方便实现,定义一个尾结点
r = first;
for (int i = 0; i < n; ++i)
{
s = new Node<T>;
s->data = a[i];
r->next = s; // 每次在插入时都会把新的结点插入到尾结点的后继中
r = s; // 更新尾结点为最新的s结点,保证最后一个是尾结点
}
r->next = NULL; // 所有插入完成后,尾结点的后继为NULL
}
头插法和尾插法是链式存储当中最常用的两种方法,其中头插法会把按照顺序进入的数据在链表里反向插入,因为每次新结点都会插入在first之后,旧的结点会后移。而尾插法正好相反,是按照顺序进入的,因为每次将新结点插入在尾结点后,又更新尾结点,所以保证了数据方向的一致。
单链表的遍历
void Printlist()
{
Node<T>* p;
p = first->next;
while (p != NULL) // p不为NULL为条件,否则指针异常
{
cout << p->data << endl;
p = p->next;
}
}
单链表中按位置查找
T getData(int i)
{
Node<T>* p;
p = first->next;
int x = 1;
while (p != NULL && x < i)
{
p = p->next;
x++;
}
if (p == NULL)throw"位置"; // 如果错误范围异常
else
return p->data;
}
单链表的按位置插入
void Insert(T a, int i)
{
Node<T>* p;
int count = 0; // 定义一个计数器,判断位置
p = first;
while (p != NULL && count < i - 1)
{
p = p->next;
count++;
} // 找到应该插入的位置
if (p == NULL)throw"位置";
else
{
Node<T>* s;
s = new Node<T>;
s->data = a;
s->next = p->next;
p->next = s;
}
}
单链表的删除操作
T Delete(int i)
{
Node<T>* p;
int count = 0;
p = first;
while (p != NULL && count < i - 1)
{
p = p->next;
count++;
}
if (p == NULL || p->next == NULL)throw"位置";
else
{
Node<T>* q;
T x;
q = p->next;
x = q->data;
p->next = q->next; // 链表删除后,要重新定向删除删除节点的上一个结点的后继
delete q;
return x;
}
return 0;
}
析构函数
~Linklist()
{
Node<T>* q;
while (first != NULL)
{
q = first->next;
delete first;
first = q;
}
}
总体而言,单链表的优势非常明显,但是实现起来指针方面使用还不很不熟练,需要多写代码来练习链表的使用,为以后学习数、图打下好的基础。
下面所有的代码:
#include<iostream>
using namespace std;
template<class T>
struct Node
{
T data;
Node<T>* next;
};
template<class T>
class Linklist
{
Node<T>* first;
public:
Linklist()
{
first = new Node<T>;
first->next = NULL;
}
Linklist(T a[], int n) //头插法
{
first = new Node<T>;
first->next = NULL;
Node<T>* s;
for (int i = 0; i < n; ++i)
{
s = new Node<T>;
s->data = a[i];
s->next = first->next;
first->next = s;
}
}
Linklist(int n, T a[]) //尾插法
{
first = new Node<T>;
Node<T>* s;
Node<T>* r;
r = first;
for (int i = 0; i < n; ++i)
{
s = new Node<T>;
s->data = a[i];
r->next = s;
r = s;
}
r->next = NULL;
}
~Linklist()
{
Node<T>* q;
while (first != NULL)
{
q = first->next;
delete first;
first = q;
}
}
int getlength()
{
Node<T>* p;
int count = 0;
p = first;
while (p != NULL)
{
p = p->next;
count++;
}
return count;
}
T getData(int i)
{
Node<T>* p;
p = first->next;
int x = 1;
while (p != NULL && x < i)
{
p = p->next;
x++;
}
if (p == NULL)throw"位置";
else
return p->data;
}
int getLocate(T x)
{
Node<T>* p;
int count = 0;
p = first->next;
while (p != NULL)
{
if (p->data == x)
return count;
else
count++;
p = p->next;
}
return 0;
}
void Insert(T a, int i)
{
Node<T>* p;
int count = 0;
p = first;
while (p != NULL && count < i - 1)
{
p = p->next;
count++;
}
if (p == NULL)throw"位置";
else
{
Node<T>* s;
s = new Node<T>;
s->data = a;
s->next = p->next;
p->next = s;
}
}
T Delete(int i)
{
Node<T>* p;
int count = 0;
p = first;
while (p != NULL && count < i - 1)
{
p = p->next;
count++;
}
if (p == NULL || p->next == NULL)throw"位置";
else
{
Node<T>* q;
T x;
q = p->next;
x = q->data;
p->next = q->next;
delete q;
return x;
}
return 0;
}
void Printlist()
{
Node<T>* p;
p = first->next;
while (p != NULL)
{
cout << p->data << endl;
p = p->next;
}
}
};
int main()
{
int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
Linklist<int>list1(10, a);
list1.Insert(100, 3);
list1.Delete(5);
list1.Printlist();
return 0;
}