目录
一、链表
链式存储特点:
- 结点在存储器中的位置是随意的,即逻辑上相邻的数据元素在物理上不一定相邻
- 每个存储结点都包含两部分:数据域和指针域
- 插入、删除不需要移动元素
- 不必事先估计存储空间
- 不可随机访问任一元素
相关名词:
单链表 | 结点只有一个指针域的链表 |
循环链表 | 首尾相接的链表 |
双链表 | 有两个指针域的链表 |
首元节点 | 链表中第一个存储实际数据元素的结点 |
头节点 | 首元结点之前附设的一个结点,指向首元节点;数据域可以放表长等信息,不计入表长度 |
头指针 | 指向链表中第一个结点(头结点、或首元结点)的指针 |
有头节点:头指针-头节点-首元节点
无头节点:头指针-首元节点
二、单链表
操作时间复杂度分析:
查找:O(n)
前插或删除:O(n)
基本操作代码:
包括带头节点链表的初始化,头插法,尾插法,顺序录入,改变某节点的值,删除节点,删除值相同节点,打印链表。
#include<stdio.h>
#include<stdlib.h>
typedef struct node { //节点类型(数据域+指针域)
int data;
struct node* next;
}node, * pnode; //将指向这种结构体的指针类型命名为pnode,等价于node*
void initlist(pnode& l) //初始化链表:创建头节点
{
l = (pnode)malloc(sizeof(node));
if (!l) return;
l->next = NULL;
}
void createlist(pnode& l) //创建链表:p指向链表最后一位,newnode指向新建节点,依次读入元素
{
pnode p = l; //p指向头节点
pnode newnode;
int len;
printf("链表长度:");
scanf_s("%d", &len);
printf("输入链表内容,空格间隔:");
for (int i = 0; i < len; i++)
{
newnode = (pnode)malloc(sizeof(node));
if (!newnode) return;
scanf_s("%d", &newnode->data);
p->next = newnode; //新节点成为p的后继,p指向新节点
p = newnode;
}
p->next = NULL;
}
void tailinsert(pnode& l, int value) //尾插法
{
pnode p = l;
while (p->next)
p = p->next; //p定位到最后一个节点
pnode newnode = (pnode)malloc(sizeof(node));
if (!newnode) return;
newnode->data = value;
newnode->next = NULL;
p->next = newnode;
}
bool insert(pnode& l, int i, int value) //在第i个节点之前插入节点,使它成为第i个节点
{
if (i < 1) return false; //i值不合法
node* p = l;
int j = 0; //当前p指的是第几个结点
while (p != NULL && j < i - 1) //循环找到第i-1个结点,即前序节点
{
p = p->next;
j++;
}
if (p == NULL) return false; //i值不合法
node* newnode = (node*)malloc(sizeof(node));
if (!newnode) return false;
newnode->data = value;
newnode->next = p->next;
p->next = newnode; //将新结点连接到p之后(先接上后面,再连上前面)
return true;
}
bool changedata(pnode& l, int pos, int value) //更改第pos个节点的数据值(头节点为第0个)
{
if (pos < 1)
return false;
pnode p = l;
int i = 0;
while (p != NULL && i != pos) {
p = p->next;
i++;
}
if (p == NULL)
return false;
p->data = value;
}
void deletesameelement(pnode& l) //删除重复元素,用三个指针
{
node* p, * q, * s; //p为“哨兵位”,后面的值与p所指的相同则应删去;s为q的前序节点;
p = l->next;
for (; p; p = p->next)
{
s = p;
for (q = p->next; q;)
{
if (p->data == q->data) //q与p值相同则删掉q
{
s->next = q->next;
free(q);
q = s->next;
}
else
{
s = q;
q = q->next;
}
}
}
}
bool deletenode(pnode& l, int pos) //删除某节点
{
if (pos < 1) return false;
node* p = l;
int j = 0;
while (p != NULL && j < pos - 1) { //循环找到第i-1个结点
p = p->next;
j++;
}
if (p == NULL || p->next == NULL) return false; //当i值为节点数+1或者再大时,不合法
node* q = p->next;
p->next = q->next;
free(q);
return true;
}
void show(pnode l) //打印链表
{
pnode p = l->next;
printf("链表:");
while (p) {
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
void main() {
pnode l; //头指针
initlist(l);
createlist(l);
show(l);
tailinsert(l, 2);
changedata(l, 2, 9);
insert(l, 1, 3);
deletenode(l, 2);
show(l);
}
练习
1.合并链表
已知单链线性表La和Lb的元素按值非递减排列。归并为Lc后也按值非递减排列
思路1:la、lb、lc三个链表。a、b指针比较值大小,在c指针处尾插
void merge(pnode l1, pnode l2, pnode l3)
{
pnode p1 = l1->next; //p1p2指向首元素
pnode p2 = l2->next;
pnode p3 = l3; //p3指向头节点
while (p1 && p2)
{
if (p1->data < p2->data) //如果p1小,串入p3
{
p3->next = p1;
p1 = p1->next;
p3 = p3->next;
}
else if (p1->data > p2->data)
{
p3->next = p2;
p2 = p2->next;
p3 = p3->next;
}
else
{
p3->next = p1;
p1 = p1->next;
p3 = p3->next;
}
}
while (p1) //剩下的连起来
{
p3->next = p1;
p1 = p1->next;
p3 = p3->next;
}
while (p2)
{
p3->next = p2;
p2 = p2->next;
p3 = p3->next;
}
}
思路2:直接把la插入到lb中
2.一元多项式的计算
一元多项式的计算
每个节点包含指数、系数、指针
3.插入排序
对无头结点的单链表插入排序
思路:将原链表第一个节点分离出来,称为有序链表;将剩下的原链表依次取出插入有序链表中
/*对不带头结点的单链表实现插入排序*/
#include<stdio.h>
#include<stdlib.h>
typedef struct node {
int data;
struct node* next;
}node;
void insert(node*& l, int value) {
node* newnode = (node*)malloc(sizeof(node));
newnode->data = value;
newnode->next = NULL;
if (!l) { //如果还没有节点,l指向第一个节点
l = newnode;
return;
}
else {
node* p = l;
while (p->next)
p = p->next;
p->next = newnode;
}
}
void sort(node*& l)
{
node* p = l->next, *q, *pre;
l->next = NULL; // 将第一个节点从乱序链表中分离出来,称为有序链表
while (p != NULL)
{
q = p->next; //保留p之后节点的信息
pre = l; //作为一个从有序链表头部开始移动的节点
if (p->data <= pre->data) //由于没有头节点,所以插入有序链表首位时要讨论
{
p->next = pre;
l = p;
p = q;
}
else
{
while (pre->next && pre->next->data < p->data) // 使pre成为插入出前续节点,当pre后一节点为空或后一节点大于等于p的data时终止
pre = pre->next;
p->next = pre->next;
pre->next = p;
p = q;
}
}
}
int main() {
node* l = NULL;
printf("输入数据,-1结束:");
int value;
scanf_s("%d", &value);
for (; value != -1;) {
insert(l, value);
scanf_s("%d", &value);
}
printf("原链表为:");
for (node* p = l; p; p = p->next) {
printf("%d ", p->data);
}
sort(l);
printf("\n新链表为:");
for (node* p = l; p; p = p->next) {
printf("%d ", p->data);
}
}
4.删除重复元素
void deletesameelement(pnode& l) //删除重复元素,用三个指针
{
node* p, * q, * s; //p为“哨兵位”
for (p = l->next; p; p = p->next)
{
for (q = p; q->next;) //每次对q后一个元素进行比较
{
if (p->data == q->next->data) //相同则删掉q->next
{
s = q->next;
q->next = s->next;
free(s);
}
else //不同q向后走
{
q = q->next;
}
}
}
}
5.简单选择排序
6.按奇偶拆分单链表
void oddoreven(pnode& l1, pnode& l2, pnode& l3)
{
pnode p1 = l2, p2 = l3;
l1 = l1->next;
while (l1)
{
if (l1->data % 2 == 1)
{
p1->next = l1;
l1 = l1->next;
p1 = p1->next;
p1->next = NULL;
}
else
{
p2->next = l1;
l1 = l1->next;
p2 = p2->next;
p2->next = NULL;
}
}
}
三、静态链表
定义:用一片连续空间(一维数组)实现链式存储。每个元素都含有数据域和指示域。
数组被区分为数据链表和备用链表,数据链表以数组末位元素为起点,next为0的元素为终点;备用链表以0号位元素为起点,next为末位元素编号的元素为终点。
(1) 静态链表既有顺序存储的优点,又有动态链表的优点。所以,它存取表中第i个元素的时间与i无关。【错】
(2) 静态链表中能容纳的元素个数的最大数在表定义时就确定了,以后不能增加。【错】
(3) 静态链表与动态链表在元素的插入、删除上类似,不需做元素的移动。【对】
代码:
//静态链表。实际是一个大型数组。0号元素为备用链表的起点,最后一位为数据链表的起点,数值最后一位指向0。用数组元素的next充当指针的职能。
#include<stdio.h>
#include<stdlib.h>
#define maxsize 50
typedef struct {
int data;
int next;
}node;//节点类型
void init(node* arr) //初始化。数据域全部为0;指针域都指向后一位;只有最后一个指针指向0
{
for (int i = 0; i < maxsize - 1; i++)
{
arr[i].next = i + 1;
arr[i].data = 0;
}
arr[maxsize - 1].data = 0;
arr[maxsize - 1].next = 0;
}
int getspace(node* arr) //取出备用链表第一个节点
{
int get = arr[0].next; //0号元素是备用链表的头
arr[0].next = arr[get].next;
return get;
}
void insert(node* arr, int pos, int val) //插入。分中间和尾部插入两种情况。
{
int newnum = getspace(arr); //得到新节点编号
arr[newnum].data = val;
//if (arr[maxsize - 1].next == 0) //插入第一个元素
//{
// arr[maxsize - 1].next = get;
// arr[get].next = 0;
// return;
//}
int i;
for ( i = arr[maxsize - 1].next; arr[i].next != pos && arr[i].next != 0; i = arr[i].next); //i初始化为第一个有效元素,
if (arr[i].next == 0) //说明到了数据链表的最后一位,用尾插法
{
arr[i].next = newnum;
arr[newnum].next = 0;
}
else //中间插入
{
arr[newnum].next = arr[i].next;
arr[i].next = newnum;
}
}
void show(node* arr) //依次打印data
{
int i;
for (i = arr[maxsize - 1].next; arr[i].next != 0; i = arr[i].next) //arr[i].next == 0 表明来到最后一个有效元素
printf("%d ", arr[i].data);
printf("%d\n", arr[i].data);
}
void delet(node* arr, int pos) //删除。pos指输出列表上的位置。要根据pos找到元素实际上在矩阵中的序号,再找到它在数据链表中的前序节点。
{
int target = arr[maxsize - 1].next;
for (int i = 1; i < pos; i++)
target = arr[target].next;
int pre;
for (pre = arr[maxsize - 1].next; arr[pre].next != target; pre = arr[pre].next); //找到前序节点
arr[pre].next = arr[target].next; //跳过该节点
arr[target].next = arr[0].next; //将该节点连接到备用链表
arr[0].next = target;
}
int main() {
node arr[maxsize];
init(arr);
for (int i = 1; i < 5; i++) {
insert(arr, i, i);
}
show(arr);
insert(arr, 2, 99);
show(arr);
delet(arr, 2);
show(arr);
insert(arr, 2, 666);
show(arr);
delet(arr, 4);
show(arr);
}
四、单向循环链表
和单链表的差别在于判别链表中最后一个结点的条件不再是“后继是否为空”,而是“后继是否为头结点”。
#include<stdio.h>
#include<stdlib.h>
typedef struct node { //节点类型
int data;
struct node* next;
}node, * pnode;
typedef struct {
pnode head;
pnode tail; //head指向头节点(无数据),tail指向最后一个节点
int size;
}list; //list起到管理整个链表的作用
bool initlist(list &l)
{
pnode p = (pnode)malloc(sizeof(list)); //创建头节点
if (!p) return false;
l.head = p; //head和tail都指向头节点
l.tail = p;
l.tail->next = l.head; //尾节点的后继节点为头节点
l.size = 0;
}
bool inserttail(list& l, int value) //尾插
{
pnode p = (pnode)malloc(sizeof(node)); //创建节点
if (!p) return false;
p->data = value;
l.tail->next = p; //将该结点接入尾部
l.tail = p; //更新tail指向
l.tail->next = l.head; //重新形成环状
l.size++; //将有效结点数加一
}
bool inserthead(list& l, int value) //头插
{
pnode p = (pnode)malloc(sizeof(node)); //创建节点
if (!p) return false;
p->data = value;
p->next = l.head->next; //新节点与头节点后的节点相接
l.head->next = p; //新节点与头节点相接
l.size++; //将有效结点数加一
}
void relist(list& l) //逆置单循环链表
{
node* p, * q, * r; //将pq的方向倒置,r用来保存后面的节点
p = l.head;
q = p->next;
r = q->next;
do {
q->next = p;
p = q;
q = r;
r = r->next;
} while (p != l.head);//当p回到原点时结束
}
void show(list l)
{
node* p = l.head->next; //指向首个有效节点
while (p != l.head) { //当未指向头节点时,输出
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
void main() {
list l;
initlist(l);
inserttail(l, 9);
inserttail(l, 2);
inserttail(l, 3);
inserttail(l, 4);
inserthead(l, 6);
show(l);
relist(l);
show(l);
}
练习
1.原地逆置
带头结点单循环链表上的逆置
void relist(list& l) //逆置单循环链表
{
node* p, * q, * r; //将pq的方向倒置,r用来保存后面的节点
p = l.head;
q = p->next;
r = q->next;
do {
q->next = p;
p = q;
q = r;
r = r->next;
} while (p != l.head);//当p回到原点时结束
}
五、双向循环链表
双向链表在结点上增加一个指针域,用来存结点的直接前驱。
代码:
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
typedef struct node {
int data;
struct node* prev;
struct node* next;
}node;
void initlist(node*& list) //初始化
{
list = (node*)malloc(sizeof(node));
if (!list) return;
list->prev = list;
list->next = list;
}
bool insert_head(node* list, int val) //头插
{
node* newnode = (node*)malloc(sizeof(node));
if (newnode == 0)
return NULL;
newnode->data = val;
newnode->prev = newnode;
newnode->next = newnode;
//以下四句话有先后次序
newnode->next = list->next;
list->next = newnode;
newnode->next->prev = newnode;
newnode->prev = list;
return true;
}
bool insert_tail(node* list, int val) //尾插
{
node* newnode = (node*)malloc(sizeof(node));
if (newnode == 0)
return NULL;
newnode->data = val;
newnode->prev = newnode;
newnode->next = newnode;
//以下四句话有先后次序,先把新节点连上,再动前面的后面,再动前面。
newnode->prev = list->prev;
newnode->next = list;
list->prev->next = newnode;
list->prev = newnode;
return true;
}
void show(node* list) //依次输出
{
node* temp = list->next;
while (temp != list) {
printf("%d ", temp->data);
temp = temp->next;
}
printf("\n");
}
void search(node* list, int target) //找到第一个值为target的位置
{
int i = 1;
for(node * temp = list->next; temp != list; temp = temp->next,i++)
if (temp->data == target) {
printf("元素%d位于第%d位", target, i);
return;
}
printf("Not found");
}
bool delet(node* list, int target) //删掉链表里第一个值为target的节点
{
int i = 1;
node* temp = list->next;
while (temp != list)
{
if (temp->data == target) {
printf("删除第%d个元素\n", i);
temp->prev->next = temp->next;
temp->next->prev = temp->prev;
free(temp);
return true;
}
temp = temp->next;
i++;
}
printf("Not found");
}
void destory(node* list) //销毁链表
{
node* p = list->next;
while (p != list) {
p = p->next;
free(p->prev); //后移,再销毁前续节点,最后销毁头节点
}
free(p);
}
int main() {
node* list;
initlist(list);
for (int i = 1; i <= 5; i++) {
insert_tail(list, i);
}
show(list);
insert_tail(list, 2);
delet(list, 2);
show(list);
search(list, 4);
}