链表基本形式
链表是一种最为简单的数据结构,它的主要目的是依靠引用关系来实现多个数据的保存。
单链表
单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。
结点结构
┌───┬───┐
│data │next │
└───┴───┘
data域–存放结点值的数据域
next域–存放结点的直接后继的地址(位置)的指针域(链域)
链表通过每个结点的链域将线性表的n个结点按其逻辑顺序链接在一起的,每个结点只有一个链域的链表称为单链表
- 建立
头插法
从一个空表开始,读取字符数组a中的字符,生成新节点,将读取的数据存放到新节点的数据域中,然后将新节点插入到当前链表的表头上,直到读完字符数组a的所有元素为止。
尾插法
该算法是将新节点插到当前链表的表尾上,为此必须增加一个尾指针r,使其始终指向当前链表的尾节点。
#include <cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
using namespace std;
typedef int ElemType;
typedef struct LNode {
ElemType data; //存放元素值
struct LNode* next;//指针域指向后继结点
}ListNode;
//头插法建表
void CreaList(ListNode * &L, ElemType a[], int n) {
ListNode * s;
L = (ListNode*)malloc(sizeof(ListNode));//创建头结点
L->next = NULL; //将头结点next域置空
for (int i = 0; i < n; i++) { //循环建立数据结点
//创建数据结点*s
s = (ListNode*)malloc(sizeof(ListNode));
//将结点s插在原开始节点之前,头结点之后
s->data = a[i];
s->next = L->next;
L->next = s;
}
}
//尾插法建表
void CreaListT(ListNode * &L, ElemType a[], int n) {
ListNode * s, * r;
L = (ListNode*)malloc(sizeof(ListNode));//创建头结点
L->next = NULL; //将头结点next域置空
r = L;//r始终指向尾结点,开始时头结点和尾结点是同一个
for (int i = 0; i < n; i++) {
s = (ListNode*)malloc(sizeof(ListNode));//创建数据结点
s->data = a[i];//数据域
r->next = s;//将s插入到r后
r = s;//使r指向尾结点
}
r->next = NULL;//尾指针指针域置空
}
//输出
void DispList(ListNode * L) {
ListNode * p = L->next;//p指向开始结点
while (p != NULL)
{
cout << p->data << " "; //输出数据域
p = p->next;
}
cout << endl;
}
int main() {
ListNode* L;
int n;
cout << "n:";
cin >> n;
int* a = new int(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
//头插法建表
CreaList(L, a, n);
cout << "头插法建表:";
DispList(L);//输出
//尾插法建表
CreaListT(L, a, n);
cout << "尾插法建表:";
DispList(L);//输出
}
增删改查是必须会的四个基础代码
增
void ListTill(int a)
{
//创建一个节点
Node* temp = ( Node*)malloc(sizeof(Node)); //此处注意强制类型转换
//节点数据进行赋值
temp->a = a;
temp->next = NULL;
//连接分两种情况1.一个节点都没有2.已经有节点了,添加到尾巴上
if (NULL == head)
{
head = temp;
// end=temp;
}
else
{
end->next = temp;
// end=temp; //尾结点应该始终指向最后一个
}
end = temp; //尾结点应该始终指向最后一个
}
删
void FrList()
{
//一个一个NULL
Node* temp = head; //定义一个临时变量来指向头
while (temp != NULL)
{
// printf("%d\n",temp->a);
Node* pt = temp;
temp = temp->next; //temp指向下一个的地址 即实现++操作
free(pt); //释放当前
}
//头尾清空 不然下次的头就接着0x10
head = NULL;
end = NULL;
}
void DeleteListTail()
{
if (NULL == end)
{
printf("链表为空,无需删除\n");
return;
}
//链表不为空
//链表有一个节点
if (head==end)
{
free(head);
head=NULL;
end=NULL;
}
else
{
//找到尾巴前一个节点
struct Node* temp =head;
while (temp->next!=end)
{
temp = temp->next;
}
//找到了,删尾巴
//释放尾巴
free(end);
//尾巴迁移
end=temp;
//尾巴指针为NULL
end->next=NULL;
}
}
改
void AddListRand(int index, int a)
{
if (NULL == head)
{
printf("链表没有节点\n");
return;
}
Node* pt = FindNode(index);
if (NULL == pt) //没有此节点
{
printf("没有指定节点\n");
return;
}
//有此节点
//创建临时节点,申请内存
Node* temp = ( Node*)malloc(sizeof(Node));
//节点成员进行赋值
temp->a = a;
temp->next = NULL;
//连接到链表上 1.找到的节点在尾部 2.找到的节点在中间
if (pt == end)
{
//尾巴的下一个指向新插入的节点
end->next = temp;
//新的尾巴
end = temp;
}
else
{
// 先连后面 (先将要插入的节点指针指向原来找到节点的下一个)
temp->next = pt->next;
//后连前面
pt->next = temp;
}
}
查
void ScanList()
{
Node* temp = head; //定义一个临时变量来指向头
while (temp != NULL)
{
printf("%d\n", temp->a);
temp = temp->next; //temp指向下一个的地址 即实现++操作
}
}
双链表
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。
建立
typedef struct Node //组成双向链表的节点
{
int data;
Node * pNext; //有两个结点
Node * pLast;
};
增删改查
void changeList(int num, int position) //修改链表中指定位置的节点
{
Node* p = pHead->pNext;
if (position > length || position <= 0)
{
cout << "over stack !" << endl;
return;
}
for (int i = 0; i < position - 1; i++)
{
p = p->pNext;
}
p->data = num;
}
void insertList(int num, int position) //插入数据
{
Node* p = pHead->pNext;
if (position > length || position <= 0)
{
cout << "over stack !" << endl;
return;
}
for (int i = 0; i < position - 1; i++)
{
p = p->pNext;
}
Node* temp = new Node();
temp->data = num;
temp->pNext = p;
temp->pLast = p->pLast;
p->pLast->pNext = temp;
p->pLast = temp;
length++;
}
void clearList() //清空
{
Node* q;
Node* p = pHead->pNext;
while (p != NULL)
{
q = p;
p = p->pNext;
delete q;
}
p = NULL;
q = NULL;
}
void deleteList(int position) //删除指定位置的节点
{
Node* p = pHead->pNext;
if (position > length || position <= 0)
{
cout << "over stack !" << endl;
return;
}
for (int i = 0; i < position - 1; i++)
{
p = p->pNext;
}
p->pLast->pNext = p->pNext;
p->pNext->pLast = p->pLast;
delete p;
length--;
}
int getItemInList(int position) //查找指定位置的节点
{
Node* p = pHead->pNext;
if (position > length || position <= 0)
{
cout << "over stack !" << endl;
return 0;
}
for (int i = 0; i < position - 1; i++)
{
p = p->pNext;
}
return p->data;
}
~List()
{
Node* q;
Node* p = pHead->pNext;
while (p != NULL)
{
q = p;
p = p->pNext;
delete q;
}
p = NULL;
q = NULL;
}
};
循环链表
//带头结点的循环单链表
#include<iostream>
using namespace std;
typedef struct LNode {
int data;
LNode* next;
}LNode, * LinkList;
void InputList(LinkList& L) {
L = (LNode*)malloc(sizeof(LNode));
L->data = NULL;
L->next = L;
LNode* p = L;
cout << "输入数据:(以9999结尾)" << endl;
int x;
cin >> x;
while (x != 9999) {
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = x;
s->next = p->next;
p = s;
cin >> x;
}
p->next = L;
}
int LocateElem(LinkList& L) {
int x, j = 1;
cout << "输入要查找的数:";
cin >> x;
LNode* p = L->next;
if (p->next == NULL)
cout << "无此数。" << endl;
while (p->next != NULL && p->data != x) {
j++;
p = p->next;
}
return j;
}
void ListInsert(LinkList& L) {
int i = 1;
LNode* p = L->next;
while (i <= LocateElem(L)) {
p = p->next;
}
LNode* s = (LNode*)malloc(sizeof(LNode));
cout << "输入需要插入的数:";
int x;
cin >> x;
s->data = x;
s->next = p->next;
p = s;
}
void PrintList(LinkList& L) {
LNode* p = L;
cout << "输出链表:" << endl;
while (p->next != NULL) {
cout << p->next->data<<'\t';
p = p->next;
}
}
void ListDelete(LinkList& L) {
LNode* p = L->next;
cout << "输入要删除的数:";
int x;
cin >> x;
while (p->data != x)
p = p->next;
LNode* s = p->next;
p->data = p->next->data;
p->next = s->next;
free(s);
}
int main() {
LinkList L;
InputList(L); //初始化建立循环单链表
PrintList(L);
ListInsert(L); //插入一个数
PrintList(L); //打印链表
ListDelete(L); //删除链表中的数
PrintList(L); //打印链表
return 0;
}
链表的用途
链表常用于在程序中临时存储一组不定长的线性数据。具有这样的特点的数据可以用链表来保存:
- 数据是逐渐增加的
- 数据是不定长的,在存储第一个数据之前难以确定一个将来一共需要存储多少数据的上限,或者虽然可以确定上限,但这个上限又比通常大部分情况下数据可能达到的长度要大得多,因而一次性按照上限把空间分配好是不划算的。而链表则可以在每次需要增加新数据时才为之申请内存,不会造成浪费,也不会因一次申请不足而使数据的数量受到限制。
- 不需要按照序号对数据进行随机访问。C++ STL 中提供了list容器,就是链表。同时STL还提供了vector容器,也可以用于处理具有上述特点的数据,而且vector还支持随机访问(即可以不考虑上述第3点要求)。但vector在增加数据时,如果原先分配的连续内存已经用完则需要重新分配内存并把原有数据复制过去,这时它的插入数据的动作时间复杂度就不是O(1)了(不是常量时间了)。因而,链表适于处理的数据除了具有上述特点外,如果还有如下第4点特征,则以链表为最佳选择了:
- 希望每次添加数据、删除数据的动作的时间复杂度都是O(1)的(常量时间)。