本篇文章结合这代码和图片进行多方面的讲解和呈现,细节的标识都以注释的形式展现,同样的,重点往往也是注释,有对比记忆点,也有一些理解的代换操作,目的是提高代码的健壮性和可读性。
#include<iostream>
//typrdef<数据类型><别名>
// lnode单链表 和sequence 顺序表
//为通俗易懂,假设int=elemtype,方便解读
using namespace std;
typedef int Elemtype;
struct Lnode {
int data;// elemtype data;定义单链表结点类型
struct Lnode* next;// struct 存放数据元素和下一个结点,而且还要留意创建对应的空间
};
typedef Lnode* linklist;
//相当于 点是lnode,独立出来的一个结点,*是用来表示单链表,是*指针首指针
/*typedef struct Lnode{
int data;
struct Londe* next;
}Lnode, *linklist;
*/
//之后的 lnode* l等价于 linklist l;
//在结构体申明的时候没有建立对应的储存空间,所以需要储存的是
//lnode* p = (lnode * )malloc(sizeof(lnode));
//可以通过下面的函数 进一步理解各自的侧重点
//Lnode* get(linklist L, int i) {是强调点不同,但是是等价的
Lnode *get(linklist L,int i){
int j = 1;
Lnode* p =L->next;
if (i == 0)
return L;
if (i < 1)
return NULL;
while (p != NULL && j < i) {
p = p->next;
j++;
}
return p;
}
//初始化不带头结点的单链表
bool initlist(linklist& L) {//防止脏数据并且取出来改动,没有取的话只是改动它的复制品
L = NULL;
return true;
}
//带头结点
bool INITlist(linklist &L) {
L = (Lnode*)malloc(sizeof(Lnode));
if (L == NULL) {
return false;
}
L->next = NULL;
return true;
}
void test(){
linklist L;
initlist(L);
}
//之后是追加结点,链表末尾增加一个结点,表尾结点的地址部分保存在空地址,此时需要将其设置为一个新增的结点的地址,
//头结点是不储存数据的,所以基本带头结点更容易写代码的操作,如果不写结点的下一块就是写数据的地方,但是又需要到时候自己开辟空间
//empty(L)的区别是 L->next or L == NULL;
//带头结点的插入典型的插入,代码核心会标注,不痛不痒的东西考虑到代码的健壮性
bool insertnode(linklist& L, int i, Elemtype e) {
if (i < 1)
return false;
Lnode* p=L;
int j=0;
while (p == NULL && j < i - 1) {
p = p->next;
j++;
}
if (p == NULL) //i值不合法
return false;
Lnode* s = (Lnode*)malloc(sizeof(Lnode));
s->data = e;
s->next = L->next;
L->next = s;
return true;
}
//compare with no fist point insert 考虑首位0的特殊性,所以在其上面加一组预处理就可以
bool listinsertno(linklist& L, int i, Elemtype e) {
if (i == 1) {
Lnode* s = (Lnode*)malloc(sizeof(Lnode));
s->data = e;
s->next = L;
L = s;//因为L永远做龙头老大
return true;
}
insertnode(L, i, e);
}
//指定结点后插入
bool insertnextnode(Lnode* L, Elemtype e) {
if (L == NULL) {
return false;
}
Lnode* p = (Lnode*)malloc(sizeof(Lnode));
if (p == NULL) {
return false;
}//这一手是预防,有时候可能分配失败,因为malloc这个单链表分配和数据直接加倍不同
p->data = e;
p->next = L->next;
L->next = p;
return true;
//如果融汇到L链表的话,借鉴insertnode
}
//前插入操作
bool insertpriornode(Lnode* p, Elemtype e) {
if (p == NULL) {
return false;
}
Lnode* s = (Lnode*)malloc(sizeof(Lnode));
if (s == NULL) {
return false;
}
s->next = p->next;
p->next = s;
s->data = p->data;
p->data = e;//鸠占鹊巢
return true;
}
//按位序删除(带头结点)
bool listdeletenode(linklist& L, int i, Elemtype &e) {
if (i < 1)
return false;
Lnode* p = L;
int j = 0;
while (p != NULL && j < i - 1) {
p = p->next;
j++;
}
if (p == NULL)
return false;
if (p->next == NULL)
return false;
Lnode* q = p->next;
e = q->data;
p->next = q->next;//三行可以简化成p->next = q->next->next
free(q);
return true;
}
//按位查找 返回第i个元素
Lnode* getelem(linklist L, int i) {//因为只是提取数据,而不是更改
//代码健壮性
if (i < 0)
return NULL;
Lnode* p = L;
int j = 0;
while (p != NULL && j < i) {
p = p->next;
j++;
}
return p;
}//平均 0(n)可以作为一个封装的操作
//带头结点尾插法建立单链表0(n)头插法
linklist createlink(linklist& L) {
int x;
L = (linklist)malloc(sizeof(Lnode));
Lnode* s, * r = L;
scanf("%d", &x);
while (x != -1) {
s = (Lnode*)malloc(sizeof(Lnode));
s->data = x;
r->next = s;
r = s;
scanf("%d", &x);
/*头插法
* L->next=null;动态分配怕有脏数据
* s->data=x;
* s->next=L->next;
* L->next=s;相当于没有r,L自动作为头的指针,更省定义的空间
*/
}//s是存放,r作为一个跟踪指针,通常和L联系的都作为一个在操作中跟踪位置的指针
r->next = NULL;
return L;
}
下面介绍双链表的操作
//双链表也就是有来有回,prior和next的链接点
typedef struct Dnode {
Elemtype data;
struct Dnode* next, * prior;
}Dnode,*dnodelist;
bool initdnodelist(dnodelist& L) {
L = (Dnode*)malloc(sizeof(Dnode));
if (L == NULL)
return false;
L->prior = NULL;
L->next = NULL;
return true;
}
bool insertnextDnode(Dnode* p, Dnode* s) {
if (p == NULL || s == NULL)
return false;
s->next = p->next;
if (p->next != NULL) {
p->next->prior = s;//预防p的next是空
}
s->prior = p;
p->next = s;//在p后面插一个s
return true;
}
bool deletenextdnode(Dnode* p) {//删除p的后继结点
if (p == NULL)
return false;
Dnode* q = p->next;
if (q == NULL)
return false;
p->next = q->next;
if (q->next != NULL)
q->next->prior = p;
free(q);
return true;
}
//封装的好处
void destorylist(dnodelist& L) {
while (L->next != NULL)
deletenextdnode(L);
free(L);
L = NULL;
}
//双链表的遍历,就是next和prior的传递 while控制!=null就可以
//双链表不可随机储存,按位查找、按值查找操作都只能用遍历的方式实现0(n)
//进一步升级就是循环单链表和循环双链表,额外的增加是在初始化的时候
bool circleINITlist(linklist& L) {
L = (Lnode*)malloc(sizeof(Lnode));
if (L == NULL) {
return false;
}
L->next = L;
return true;//判断属性的时候就看是否能回到表首
}
bool circleinitdnodelist(dnodelist& L) {
L = (Dnode*)malloc(sizeof(Dnode));
if (L == NULL)
return false;
L->prior = L;
L->next = L;//全部回到自身,对比理解
return true;
}
bool insertnextpro(Dnode* p, Dnode* s) {
s->next = p->next;
p->next->prior = s;
s->prior = p;
p->next = s;//因为双链表的特殊性,就自带有健壮性,所以在操作的时候就省了不少事情
}
//静态链表],分配一整片连续的空间,各个结点集中安置 且静态链表都是固定
#define maxsize 10
struct node {
Elemtype data;
int next;
};
void testsinklist() {
struct node a[maxsize];
}
//同理
typedef struct {
Elemtype data;
int next;
}sLinkList[maxsize];
//
将上面的讲解归类成图像的理解
下面加入一个顺序表和链表的对比思考,套用一下讲解代码和答题模板,结合时间复杂度和健壮性还有运算逻辑。
逆转链表
作为常考思维的翻转链表,有3种实现方式
typedef struct LinkList{
struct LinkList *next;
int data;
}LinkList;
//头插法创建单链表 不带头结点(1+1 指针数,1个保存头结点+1个遍历指针)
LinkList *creatLinkList(int n) {//创建一个含n个元素的单链表
LinkList *head = (LinkList *)malloc(sizeof(LinkList));
head->data = 0;
LinkList *q ;
for(int i = 1;i < n;i++) {
q = (LinkList *)malloc(sizeof(LinkList));
q->data = i;
q->next=head;
head = q;
}
return head;
}
//遍历单链表(不带头结点)
void PrintLinkList(LinkList *L){
while (L != NULL) {
printf("%d", L->data);
L = L->next;
}
return;
}
//单链表反转方法一: 尾插法(递归实现)(1+1 指针数,1个指针参数+1个结点指针p)暴力搜
LinkList *ReverseList (LinkList *L){
LinkList *p = NULL;
if (L == NULL) return NULL; //判空
if (L->next == NULL) {
p = L;
return p; // 递归到底,返回第一个结点p
}
p = ReverseList(L->next); //递归,得到第一个结点p
L->next->next = L; //设置当前结点在后置结点之后
L->next = NULL; //链表尾置空
return p;
}
//单链表反转方法二: 头插法(循环实现)(1+2 指针数,1个指针参数+2个跟踪指针)
//也就是尾插法从头结点开始的逆转
LinkList *ReverseList_Loop(LinkList *L){
if (L == NULL) return NULL; //判空
LinkList *q = NULL;
while (L != NULL) {
LinkList *p = L; //创建指针p指向当前结点
L = L->next; //当前结点右移
p->next = q; //指针p的next域指向q,p一直是逆序的一部分链表
q = p; //q换到p的位置,开始下一轮
}
return q;
}
//单链表反转方法三:指针反转 (循环实现) (3个指针遍历一次)
LinkList *ReverseList_Pointer(LinkList *L){
if (L == NULL) return NULL; //判空
//三个指针 pl用来断开后面的并保存,p和pr用来指针反转
LinkList *pr = L;
LinkList *p = pr->next;
LinkList *pl = p->next;
pr->next = NULL; //头部置空
while (p) {
p->next = pr; //p和pr指针反转
//依次往后遍历
pr = p;
p = pl;
if (p!=NULL) pl = pl->next;
}
return pr;
}
void Reverse() {
LinkList *head = creatLinkList(5);//创建一个含5个元素的单链表
printf("创建后遍历:\n");
PrintLinkList(head);
printf("\n单链表递归反转后:\n");
head = ReverseList(head);
PrintLinkList(head);
printf("\n单链表接着循环反转后:\n");
head = ReverseList_Loop(head);
PrintLinkList(head);
printf("\n单链表再指针反转后:\n");
head = ReverseList_Pointer(head);
PrintLinkList(head);
printf("\n");
}