链表的两种创建方法——头插法与尾插法
链表创建的两种方法
引言
首先讲述了我自己在动态实现数据结构的时候遇到的大坑结构体指针问题,随后就是头插法和尾插法的介绍。
结构体指针
引言
首先需要讲述的是一个让我改了一天半的bug。实际上半天的时候就已经发现了二级指针的问题,但是并没有深究,以为是自己对指针运用不够熟练,就略微重温了一下指针的比较浅层的相关内容,然后第二天的两个程序链栈和链队列都出现了段错误和垃圾值问题,改了半天才发现不仅仅是二级指针的问题,主要其实是结构体指针的问题。在此,对这个坑进行记录,补充知识,以防再犯错。
正文
结构体指针在常见的基础数据结构如:动态数组、链表、栈、队列、树、图中都是很常见的。
关于结构体指针也很简单,就是一个指针,它所指向的类型是结构体类型,通过该指针可以访问结构体变量的内部成员如(*p).age访问结构体变量的age成员,其中 *p=结构体变量。
我们通常通过这种方法在主函数与其他函数之间进行传址操作。通过结构体指针改变结构体变量成员的值,但是有时候并不会注意到它并不能改变结构体变量本身的值,这种错误常发生在动态分配内存上。拿链栈举例,我们试图创建一个链栈,对指向栈顶的指针的初始化的init函数中,我们需要对指向栈顶指针分配内存,此时我们可能会传入在主函数里定义好的头指针。
void init_(Stacklink front)
{front = (Stacklink)malloc(sizeof(StackNode));front->pNext = NULL;
}
上面这段代码看似没有任何错误,实际上这样并不能改变改变front的地址,我们想要改变front值应该通过传指针的指针,即二级指针。下面是一个简单例子来证明。
#include <stdio.h>
#include <malloc.h>
typedef struct student{int age;int num;
} stu, *p_stu;
void change(p_stu ps)
{ps = (p_stu)malloc(sizeof(stu));printf("address in change(),the value of ps:%p\n", ps);
}
int main()
{p_stu ps = NULL;printf("address in main(),before changing the value of ps:%p\n", ps);change(ps);printf("address in main(),after changing the value of ps:%p\n", ps);
}
结果如下
而通过传结构体指针的指针得到下面结果
#include<stdio.h>
#include<malloc.h>
typedef struct student{int age;int num;
} stu, *p_stu;
void change(p_stu* pps)
{*pps = (p_stu)malloc(sizeof(stu));printf("address in change(),the value of ps:%p\n", pps);printf("address in change(),the value of ps:%p\n", *pps);
}
int main()
{p_stu ps = NULL;printf("address in main(),the value of ps:%p\n", ps);change(&ps);printf("address in main(),after changing the value of ps:%p\n", ps);
}
结果如下
可以发现传入二级指针,成功实现了对结构体指针变量的地址分配。
所以如果想改变结构体指针的地址应该传入二级指针->结构体指针的指针。
头插法
依据上面说到的结构体指针问题,为了增加可读性,这里创建链表都用返回值来实现而不是二级指针
头插法:顾名思义,在头节点后面插入新的节点,每个新产生的节点都会被接在头节点后面。
接下来给出关于头插法的图例解释。
代码中关键的步骤在图中给出了,如果能理解,头插法创建链表就很简单了
#include <stdio.h>
#include <malloc.h>
#include <stdbool.h>
#include <windows.h>
typedef struct NODE{int data;struct NODE* pNext;
}node; //定义链表节点的类型
node* create_node(int size); //创建链表
void print_node(node* pHead); //打印链表
int main()
{node *pHead;int length;printf("please put the length of list:");scanf("%d", &length);pHead = create_node(length);print_node(pHead);
}
node* create_node(int size)
{int i,val;node *phead = (node *)malloc(sizeof(node)); //创建头节点的“分身”,利用“分身”创建一个链表之后返回分身地址,pHead就构成了一个链表的头节点phead->pNext = NULL; //要记得置NULL,一开始写的时候忘记了,导致出现了段错误for (i = 0;i<size; i++) //根据输入的length创建新节点{node *pNew = (node *)malloc(sizeof(node));scanf("%d", &val);pNew->data = val;pNew->pNext = phead->pNext; //先让新节点指针指向头节点后继节点phead->pNext = pNew; //再让头节点指向新节点}return phead;
}
void print_node(node *pHead)
{printf("It's the time to print node.\n");node *q = pHead->pNext;while (q != NULL){printf("%d\n", q->data);q = q->pNext;}q = NULL;
}
代码输出如下
可以注意到,这里输出的序列是输入序列的逆序,原因是因为从头指针开始遍历,而每次插在头节点后面的结点是最后创建的结点,符合先进后出,由此我们可以想到链栈应该是用头插法创建链表了。
同时写这段代码的时候,又开始犯创建之后指针不初始化的错误了,导致尾节点的指针域是个野指针,循环无法结束。关于指针的错误真的是很常见而且很难找,所以建议大家都建立指针之后先根据要求置NULL,再进行操作。
尾插法
尾插法:每个新节点都插在表尾,因此叫做尾插法。
#include <stdio.h>
#include <malloc.h>
#include <stdbool.h>
#include <windows.h>
typedef struct NODE{int data;struct NODE* pNext;
}node; //定义链表节点的类型
node* create_node(int size); //创建链表
void print_node(node* pHead); //打印链表
int main()
{node *pHead;int length;printf("please put the length of list:");scanf("%d", &length);pHead = create_node(length);print_node(pHead);
}
node* create_node(int size)
{int i,val;node *phead = (node *)malloc(sizeof(node)); //创建头节点的“分身”,利用“分身”创建一个链表之后返回分身地址,pHead就构成了一个链表的头节点phead->pNext = NULL; node *p = phead;for (i = 0;i<size; i++) //根据输入的length创建新节点{node *pNew = (node *)malloc(sizeof(node));scanf("%d", &val);pNew->data = val;p->pNext = pNew; //先让新节点指针指向头节点后继节点p = pNew; //再让头节点指向新节点}p->pNext = NULL;//尾节点要置NULL否则野指针循环无法结束return phead;
}
void print_node(node *pHead)
{printf("It's the time to print node.\n");node *q = pHead->pNext;while (q != NULL){printf("%d\n", q->data);q = q->pNext;}q = NULL;
}
可以发现这里的链表输出是正序的,符合先进先出,可以用来构造链式队列。
总结
1.头插法是利用头节点的指针域始终指向头节点的后继节点的特点,来对新插入的节点完成前驱后继节点的设置,链表内数据顺序是与输入顺序逆序的。
2.尾插法是头节点二号分身来控制新节点的插入位置,链表内数据顺序是正序的。
3.依据尾插法,当我们需要对整个链表进行操作的时候通常创建一个新的节点,让它与头节点相等,来实现遍历。
链表的两种创建方法——头插法与尾插法相关推荐
- C语言实现单链表(带头结点)的基本操作(创建,头插法,尾插法,删除结点,打印链表)
http://blog.csdn.net/xiaofeige567/article/details/27484137 C语言实现单链表(带头结点)的基本操作(创建,头插法,尾插法,删除结点,打印链表) ...
- 数据结构之头结点链表的三种插入方式(头插法,尾插法,在pos处插入)
创建头结点 流程:首先创建头结点表指针并为其分配空间--并将头结点指向空,防止出现段错误. 代码: //创建头结点 Node* Create_List () {//创建头结点Node* list = ...
- day030进程的两种创建方法,验证进程的空间隔离,join等待子进程
本节内容: 1.操作系统的简单介绍 2.进程的两种创建方法 3.进程之间是空间隔离的, 参考文章: 一.操作系统的简单介绍 1.操作系统简单介绍 操作系统就是一个协调.管理和控制计算机硬件资源和软件资 ...
- 线性表基本操作,单链表的建立(头插法,尾插法)、插入、删除、遍历操作的实现(c++ 数据结构 实验二)
大学数据结构课程的实验题目,掌握线性表的链接存储结构,用c++语言描述 一.实验要求 1.分别用头插法和尾插法建立一个含有若干结点的单链表 2.对已建立的单链表进行插入.删除.遍历输出等操作 二.代码 ...
- java单向链表中的头插法与尾插法
单链表属于数据结构中的一种基本结构,是一种线性结构,在此使用Java对其中的头插法以及尾插法进行解释.数据结构 首先定义好链表中的节点类: 其中,data表明节点所存放的数据,next表明指向下一节点 ...
- 头插法、尾插法建立单链表(含详细注释C++)
一.信息须知: 1:malloc()函数,头文件为cstdlib. 用法: node *p=(node *)malloc(sizeof(node)). 作用:分配一个node类型大小的内存空间, 并把 ...
- 头插法、尾插法建立单链表
头插法.尾插法建立单链表 #include<stdio.h> #include<stdlib.h> #define TRUE 1 #define FALSE 0 #define ...
- 头插法与尾插法建立单链表
头插法与尾插法建立单链表 import java.util.*; class Node {int value;Node next;Node(int x) {value = x;} } public c ...
- 单链表的头插法与尾插法代码实现及详解
1. 头插法 2. 尾插法 3. 代码测试 1. 头插法 头插法: 从一个空表开始,重复读入数据,生成新结点,将读入数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头结点之后,直到读入结 ...
最新文章
- (0034) iOS 开发之UIView动画(过渡效果)
- python四十七:在子类中调用父类方法
- onblur 对象失去焦点事件
- 车载wince系统刷界面ui_2020年值得关注的10个UI设计趋势!
- Azure SQL的DTU和eDTU到底是个什么鬼
- 2019年7月前CSDN最新排名
- mysql 5.7.13 mac_Mac 安装mysql 5.7.13版本 登录及改密码
- 事务处理与事务的隔离级别
- for循环和while循环
- ubuntu16.04安装pycharm,并设置快捷启动方式
- redis 内存管理分析
- javascript return
- git学习(九)跨团队协作-非团队成员参与git项目开发
- getshell工具下载批量ecshop4.0全版本通杀工具下载
- HTML语言可以编写静态网页吗,使用html开发静态网页 html静态网页设计大作业
- [项目管理]-第十二章:项目监督和控制
- 功放的工作原理与作用
- 编写一个完整的应用程序:从键盘上读入一句英文(只包含英文字母、空格、半角逗号和半角 句号,如:I will choose the presentation..先将该英文句子首字母大写再把句子逆序输出。
- Leetcode之机器人大冒险
- 区块链技术解决投行电子底稿监管痛点 中国证券业协会在“中证链”发布首个应用