顺序表-单链表-循环链表
1 基本概念
1.1 数据
可以存储在计算机中,并且能被识别的(文字, 声音, 图像,视频)。
1.2 数据结构
数据逻辑关系(线性关系,非线性关系), 数据存储关系(顺序存储、链式存储), 算法。
1.3 算法
做某一件事情的过程(广义算法), 排序, 查找。
1.4 时间复杂度
时间复杂度一般指的是代码的语句执行总次数,称为语句频度。

1.5 空间复杂度
就是一段程序运行时所需的内存字节量。
2 线性表
对于一组拥有n个数据元素的线性表,其严格数学定义是:其中任何一个数据元素
,有且仅有一个直接前驱
,有且仅有一个直接后继
。首元素
无直接前驱,尾元素
无直接后继。
满足这种数学关系的一组数据,当中的数据是一个挨着一个的,常被称为一对一关系。反之,如果数据之间的关系不是一对一的,就是非线性的。
数据定义必须一对一关系, 并且除了首尾元素, 所有元素有且只有直接前驱和后继。
如线性表的数据用 顺序存储,那么就是顺序表。
链式存储线性表就是链表。
3 顺序表SeqList
顺序存储就是将数据存储到一片连续的内存中,在C语言环境下,可以是具名的栈数组,或者是匿名的堆数组。
顺序表:数据必须是连续,一个数据接着一个数据。
比如用顺序存储 一组随机数10个。
顺序表删除和插入数据的时候要移动数据(移位)。
优点:连续空间-节省空间, 查询速度快。
缺点:插入和删除数据需要大量移位操作。
3.1 设计一个顺序表
struct Sqlist{
int length; //当前顺序表中数据个数
int size; //当前顺序表容量
int *data; //申请堆空间数组
};
//设计操作顺序表的接口
struct Sqlist * createSq(int size);
bool destroySq(struct Sqlist* sq);
bool insertSq(struct Sqlist* sq, int pos, int data);//在指定位置pos插入数据
bool deleteSq(struct Sqlist* sq, int data);
void displaySq(struct Sqlist* sq);
3.2 细节⭐
- Sqlist里面的data也要malloc。
- 插入数据需要将数据从后面开始往后移,因为从前面往后移会覆盖原先的数据,出现无效移动。
- 删除时需要注意len需要-1。
4 单链表
链式存储线性表数据, 数据比较零散存储, 在每一个数据中包含一个指针域(指向下一个或者上一个数据)。
4.1 设计单链表结构体
struct Node{
int data; //数据域
struct Node *next ;//指针域
};
4.2 链表接口设计
//创建节点--链表
struct Node* create_node(int data);
//销毁链表
bool destroy_node(struct Node* head);
//插入节点
bool insert_node_head(struct Node* head, struct Node* newNode);//头插法
bool insert_node_tail(struct Node* head, struct Node* newNode);//尾插法
//删除节点
bool delete_node(struct Node *head, int data);
//遍历节点
void display_node(struct Node* head);
4.3 细节⭐
- malloc是⭐#include <stdlib.h>头文件里面的。
- 在单链表中,⭐head 通常是一个哨兵节点,它并不存储数据,而是指向链表的第一个数据节点。因此,head 的 next 指针指向实际的数据节点。
- 在main函数里面需要先malloc哨兵节点head,然后再创建节点来插入节点。
- 销毁单链表时,需要
- 在销毁节点前保存下一个节点的Node*值
- 然后销毁
- 最后移动节点至下一个节点(也就是销毁前保存好的节点)
- 头插入链表时需要注意要插在head哨兵节点后面,head下一个节点的前面。
- 删除节点需要
- 删除节点前保存要删除的节点
- 删除节点后(即将前节点的next指向要删除节点的next)
- 销毁要删除的节点(之前保存好的)
- 遍历节点从head->next开始。
4.4 作业
4.4.1 翻转链表
用链表存储一组数据比如1,2,3,4,5,请实现链表倒序变为5,4,3,2,1.
4.4.1.1 法一(老师)
额外使用了新的空间



代码:
#include <stdio.h>
#include <stdlib.h>
struct Node{
int data;
struct Node* next;
};
struct Node* create_node(int data){
struct Node* newNode = malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
//显示链表元素
void display_node(struct Node *head){
if(head == NULL) return ;
head = head->next;
while(head != NULL){
printf("%d ", head->data);
head = head->next;
}printf("\n");
}
int main(void){
struct Node *head = create_node(0);
for(int i=1; i<=5; i++){
struct Node *newNode = create_node(i);
struct Node *p = head;
while(p->next != NULL) p = p->next; //把p移动到链表末尾
p->next = newNode;
}
display_node(head);
//倒序
struct Node* thead = create_node(0);
while(1){
//把head后面的节点脱离链表
struct Node* p = head->next;
if(p==NULL)
{
//将thead的链表挪到head后面,并销毁thead
head->next = thead->next;
free(thead);
break;
}
//更改p节点位置前,保存p->next,用于下一次循环
head->next = p->next;
//把p节点接入thead后面
//使用头插法--将p节点头插入thead链表
p->next = thead->next;
thead->next = p;
}
display_node(head);
//display_node(thead);
}
4.4.1.2 法二(自己写的)

(自己写的代码)
sLList.c
void reversal_node(Node **head)
{
Node *p = (*head)->next; //⭐⭐*head需要加括号
Node *r = p->next; //先保留p的next,因为p->next需要指向NULL,表示末尾
p->next = NULL;
Node *temp = NULL; //用于循环时使用
while(r != NULL) //我们可以看到规律:各个表达式首尾相连
{
temp = r->next; //先存储r->next,表示后一个数据
r->next = p; //将r->next指向前一个数据
p = r; //p往前挪,仍表示前一个数据
r = temp; //r往前挪,表示当前需要修改指针的数据
}
(*head)->next = p; //修改head->next的指向
}
main.c
Node *head = (Node *)malloc(sizeof(Node)); // 创建
head->next = NULL;
int len = 0;
printf("请输入要添加数据的个数:");
scanf("%d", &len);
for (int i = 0; i < len; i++)
{
insert_node_head(head, create_node(random() % 100)); // 插入
}
display_node(head); // 显示
printf("------翻转------\n");
reversal_node(&(head));//⭐传入地址,来改变main的head值,而不是只更改翻转函数内部head值
display_node(head);
if (destroy_node(head)) // 销毁
printf("成功销毁\n");
4.4.2 交换节点

sLList.c
// 自己写的交换节点代码
// 交换data这个节点和data的下一个节点
bool swap_node_data(Node **head, int data)
{
Node *p = (*head);
while (p != NULL && p->next != NULL)
{
if(p->next->next==NULL) return false;
if (p->next->data == data)
{
swap_node(&p);
return true;
}
p = p->next;
}
return false;
}
//自己写的
//交换节点的核心代码
//交换的是p->next和p->next->next
bool swap_node(Node **p)
{
Node *r = (*p)->next; //⭐⭐更改后续指针之前保存后续指针的值
Node *q = r->next;
r->next = q->next;
q->next = r;
(*p)->next=q;
return true;
}
main.c
Node *head = (Node *)malloc(sizeof(Node)); // 创建
head->next = NULL;
int len = 0;
printf("请输入要添加数据的个数:");
scanf("%d", &len);
for (int i = 0; i < len; i++)
{
insert_node_head(head, create_node(random() % 100)); // 插入
}
display_node(head); // 显示
printf("请输入要交换的位置的第一个数据:");
int swap_data = 0;
scanf("%d", &swap_data);
printf("------交换节点------\n");
swap_node_data(&(head), swap_data);//⭐传入地址
display_node(head);
if (destroy_node(head)) // 销毁
printf("成功销毁\n");
4.4.3 冒泡排序
- 使用了交换节点的函数。
- 注意,p从head开始
- 因为要比较的数据是p->next->data和p->next->next->data。
- 这样方便保存要交换的位置的前一个节点。
sLList.c
// 冒泡排序
bool sort_node(struct Node *head)
{
bool flag = false;
if(head==NULL) return false;
do
{
flag = false;
Node *p = head;
if(p==NULL) return true;
while (p->next != NULL&&p->next->next!=NULL)
{
if (p->next->data > p->next->next->data)
{
// 只交换节点的数据
/* int temp = p->data;
p->data = p->next->data;
p->next->data = temp;
*/
swap_node(&p);
flag = true;
// display_node(head);
}
p = p->next;
}
} while (flag);
}
main.c
Node *head = (Node *)malloc(sizeof(Node)); // 创建
head->next = NULL;
int len = 0;
printf("请输入要添加数据的个数:");
scanf("%d", &len);
for (int i = 0; i < len; i++)
{
insert_node_head(head, create_node(random() % 100)); // 插入
}
display_node(head); // 显示
printf("------排序------\n");
sort_node(head);
display_node(head);
if (destroy_node(head)) // 销毁
printf("成功销毁\n");
5 单向循环链表
5.1 设计一个链表结构体
struct CNode{
int data;
struct CNode* next;
};
5.2 创建节点函数
struct CNode* create_cnode(int data){
struct CNode* node = malloc(sizeof(struct CNode));
node->data = data;
node->next = node;
}
5.3 插入节点
//头插法
bool insert_cnode_head(struct CNode* head, struct CNode* newNode)
{
if(head == NULL || newNode == NULL) return false;
newNode->next = head->next;
head->next = newNode;
}
//尾插法
bool insert_cnode_tail(struct CNode* head, struct CNode* newNode)
{
//找到尾
struct CNode* p = head;
while(p->next != head) p = p->next;
insert_cnode_head(p, newNode);
}
5.4 删除节点
struct CNode* delete_cnode(struct CNode*head, int data)
{
if(head == NULL) return NULL;
if(head->data == data) //头是要删除的数据
{
struct CNode* p = head->next;
while(p->next != head) p = p->next;
p->next = head->next;
free(head);
return p->next;
}
struct CNode *p = head;
while(p->next != head){
if(p->next->data == data){
struct CNode* t = p->next;
p->next = t->next;
free(t);
return head;
}
p = p->next;
}
return head;
}
5.5 作业
//注意:以下为核心代码,非全部代码
5.5.1 单向循环链表应用
1.设计一个链表存储1-15自然数 ,实现从头开始1,2,3第三个节点删除,后面继续数1,2,3,删除数到3所在的节点以此类推,最后剩下的一个节点输出
main.c
CNode *head = create_cnode(1);
for (int i = 15; i > 1; i--)
{
insert_cnode_head(head, create_cnode(i));
}
display_cnode(head);
CNode *p = head;
int flag = 0;
while (p->next != p)
{
flag++;
if (flag == 2)
{
printf("delete:%d,\t", p->next->data);
head = delete_cnode_by_pointer(head, p);
display_cnode(head);
flag = 0;
}
p = p->next;
}
printf("result:\t\t");
display_cnode(head);
/*
printf("请输入你要删除的数据:");
int del = 0;
scanf("%d", &del);
head = delete_cnode_by_data(head, del);
display_cnode(head);
*/
CLinkedList.c
#include "CLinkedList.h"
//创建
struct CNode* create_cnode(int data)
{
CNode *n=(CNode*)malloc(sizeof(CNode));
if(n==NULL) return n;
n->data=data;
n->next=n;
return n;
}
//插入
//头插
bool insert_cnode_head(struct CNode* head, struct CNode* newNode)
{
if(head==NULL||newNode==NULL) return false;
newNode->next=head->next;
head->next=newNode;
return true;
}
//尾插
bool insert_cnode_tail(struct CNode* head, struct CNode* newNode);
//删除节点
struct CNode* delete_cnode_by_data(struct CNode*head, int data)
{
CNode *p=head;
if(p==NULL) return head;
//如果要删除头节点
else if(head->data==data)
{
while(p!=NULL&&p->next!=head) p=p->next;
p->next=head->next;
free(head);
return p->next;
}
//如果删除的不是头节点
else
{
while(p->next!=head)
{
if(p->next->data==data)
{
CNode *r=p->next;
p->next=p->next->next;
free(r);
return head;
}
else p=p->next;
}
}
}
//删除p->next节点
struct CNode* delete_cnode_by_pointer(struct CNode*head,struct CNode*p)
{
if(head==NULL) return head;
//如果要删除头节点
else if(p->next==head)
{
p->next=head->next;
free(head);
return p->next;
}
//如果删除的不是头节点
else
{
CNode *r=p->next;
p->next=p->next->next;
free(r);
return head;
}
}
//遍历
void display_cnode(CNode* head)
{
if(head==NULL) return;
printf("display:");
CNode *p=head;
do
{
printf("%d ",p->data);
p=p->next;
}while(p!=head);
printf("\n");
}
5.5.2 判断有没有环
检测一个单链表是否存在环(思考---面试问题)
main.c
// 快慢速度问题
srand(time(NULL));
// 有环链表
CNode *head_circle = (CNode *)malloc(sizeof(CNode));
head_circle->data = 1;
head_circle->next = NULL;
create_circle(head_circle);
// display_circle(head_circle);
// 无环链表
CNode *head_list = (CNode *)malloc(sizeof(CNode));
head_list->data = 1;
head_list->next = NULL;
create_list(head_list);
//display_list(head_list);
if(judge_circle(head_list)==false) printf("head_list是无环链表\n");
else printf("head_list是有环链表\n");
if(judge_circle(head_circle)==false) printf("head_circle是无环链表\n");
else printf("head_circle是有环链表\n");
CLinkedList.c
//---------------------------------------------------有环链表代码
//创建链表节点
static CNode* create_node(int data)
{
CNode *n=(CNode*)malloc(sizeof(CNode));
n->data=data;
n->next=NULL;
return n;
}
//插入节点
static void insert_node(CNode* head,CNode* p)
{
p->next=head->next;
head->next=p;
}
//创建有环链表
void create_circle(CNode* head)
{
for(int i=6;i>1;i--)
{
insert_node(head,create_node(i));
}
CNode *end=head,*p=head;
while(end->next!=NULL)
{
if(p->data<3) p=p->next;
end=end->next;
}
end->next= p;
}
//检测有环链表,输出元素检查
void display_circle(CNode* head)
{
CNode *p=head;
for(int i=0;i<15;i++)
{
printf("%d ",p->data);
p=p->next;
}
printf("\n");
}
//---------------------------------------------无环链表代码
//创建无环链表
void create_list(CNode* head)
{
for(int i=6;i>1;i--)
{
insert_node(head,create_node(i));
}
}
//检测无环链表,输出元素检查
void display_list(CNode* head)
{
CNode *p=head;
while(p!=NULL)
{
printf("%d ",p->data);
p=p->next;
}
printf("\n");
}
//-----------------------------------------判断有无环
//有环就返回true,否则返回false
bool judge_circle(CNode* head)
{
CNode* fast=head,*slow=head;
int flag=0;
while(fast!=NULL&&slow!=NULL)
{
flag++;
//偶数的时候slow再往后娜
if(flag%2==0) slow=slow->next;
fast=fast->next;
if(slow==fast) return true;
}
return false;
}
6 双向循环链表
6.1 定义
每个节点中包含一个前驱指针, 和一个后继指针。
双向链表结构体如下:
struct Dnode {
struct Dnode* prev;//指向前驱
struct Dnode* next;//指向后继
int data; //数据
}
6.2 创建节点
//创建节点
Dnode *create_dnode(int data)
{
Dnode *n=(Dnode*)malloc(sizeof(Dnode));
n->data=data;
n->prev=n;
n->next=n;
return n;
}
6.3 插入节点
6.3.1 头插法
6.3.1.1 只有头结点时插入节点

6.3.1.2 有2个或以上的结点时插入节点

6.3.1.3 代码
//头插法
bool insert_dnode_head(Dnode* head,Dnode* p)
{
if(head==NULL||p==NULL) return false;
//只有一个头节点时
if(head->next==head)
{
p->next=head->next;
p->prev=head->prev;
head->next=p;
head->prev=p;
return true;
}
else
{
p->next=head->next;
p->prev=head;
head->next->prev=p;
head->next=p;
return true;
}
}
6.3.2 尾插法
只有头节点时插入的原理与头插法一致。
有2个或以上的节点时插入节点:

代码:
//尾插法
bool insert_dnode_tail(Dnode* head,Dnode* p)
{
if(head==NULL||p==NULL) return false;
//只有一个头节点时
if(head->next==head)
{
p->next=head->next;
p->prev=head->prev;
head->next=p;
head->prev=p;
return true;
}
else
{
p->next=head;
p->prev=head->prev;
head->prev->next=p;
head->prev=p;
return true;
}
}
6.4 删除节点
6.4.1 若删除头节点
需要注意此时是只有一个头节点还是不止一个头节点,需要分情况讨论。
- 如果不止一个头节点:

- 如果只剩下一个头节点,则只需要free(head);
6.4.2 删除非头节点

6.4.3 代码
//删除节点
Dnode* delete_dnode(Dnode* head,int data)
{
if(head==NULL) return NULL;
Dnode *p=head;
//如果删除的是头节点且此时只有一个节点
if(p->data==data&&p->next==p)
{
free(head);
return NULL;
}
//如果删除的是头节点且此时不止一个节点
if(p->data==data&&p->next!=p)
{
Dnode *tail=head->prev;//最后一个节点
tail->next=head->next;
head->next->prev=tail;
free(head);
return tail->next;
}
//如果删除的不是头节点
else
{
p=p->next;
while(p!=head)
{
if(p->data==data)
{
p->prev->next=p->next;
p->next->prev=p->prev;
free(p);
return head;
}
else p=p->next;
}
}
}
本文来自博客园,作者:cyt2730,转载请注明原文链接:https://www.cnblogs.com/cytwjyy/p/18874481

浙公网安备 33010602011771号