链表的建立和操作的讲解及其code

本篇文章结合这代码和图片进行多方面的讲解和呈现,细节的标识都以注释的形式展现,同样的,重点往往也是注释,有对比记忆点,也有一些理解的代换操作,目的是提高代码的健壮性和可读性。

#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");
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

磊哥哥讲算法

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值