前文:最近发现基础好重要,学过的容易忘,刷letCode题时候,做到了跟数据结构相关的链表,树才发现,大学学的,都忘了,没办法,选择了代码这条路,就要重新找回来基础,大学不努力,毕业徒伤悲,出来混总是要还的。
最近在看到单链表时候,对指针创建,跟结构体对齐内存等遇到了好多新鲜的知识点,让我想起了abandon这个当初学的第一个词,但是wolf能回头吗?代码都搞不定,以后怎么娶媳妇,怀着这种信念的我才继续坚持的学下去,马上要学到了树的知识,回顾下链表的基础点,做一篇博客,方便以后记忆,我自横刀向天笑,去留肝胆两昆仑!!!
链表:链式存储结构,又称单链表,存储逻辑为一对一的逻辑存储关系。顺序表是按照顺序存储,方便查看,但是链表的存储物理位置是随机的,相互之间靠着指针指向建立联系,如下图所示。
图上可以看出,链表存储位置是随机的。
链表的节点
链表的数据存储由两部分构成,数据域和指针域。
数据域:数据元素本身所在的区域称为数据域。
直接指向后继元素的指针称为指针域。
下面是我盗来的一个图解
链表中每个节点的具体实现,需要使用 C 语言中的结构体,JAVA则需要自己创建类的对象。
C的代码实现如下:
typedef struct Link{
char elem; //代表数据域
struct Link * next; //代表指针域,指向直接后继元素
}link; //link为节点名,每个节点都是一个 link 结构体
JAVA则是创建一个类:
class Node {
Node next =null;//节点引用,指向下一个节点
int data;//节点的对象,内容
public Node(int data){
this.data=data;
}
}
了解了链表数据存储后,看下链表的组成结构。
链表里主要组成为头结点,首元节点,头指针,其他结点。
头指针:一个普通的指针,特点是永远指向链表的第一个结点的位置,头指针用于指明链表的位置,便于后期找到链表并使用表中的数据。
节点:链表中的节点又细分为头节点、首元节点和其他节点:
结构如图所示
注意:链表中有头节点时,头指针指向头节点;反之,若链表中没有头节点,则头指针指向首元节点。
此处头节点是一个比较抽象的东西,它是一个虚拟的节点,可以存在,可以不存在,头节点的主要作用,并不是所谓的标识,而要使链表的操作更加容易,操作方式更加统一。
对于没有表头结点的链表,如果对其插入结点时,需要考虑是在表头插入还是表中插入,需要分不同的情况进行判断。
而如果有了表头结点,执行插入时,该判断可以省去,这样,代码就简化了,而付出的成本,仅仅是固定的一个结点的存储空间。
详情可参考:https://blog.csdn.net/bg2wlj/article/details/52300565
看完链表构成,下面开始用代码进行初始化实现操作。
C语言实现:
链表的创建:
1、声明一个头指针,有必要的话,再声明一个头节点。
2、创建多个存储节点,并建立之间的联系。
创建一个含有头节点的链表
link *initLink(){
link *p = (link*)malloc(sizeof(link));//创建一个指针指向头节点
link *temp=p;//声明一个指针指向头结点,用于遍历链表
for(int i=1;i<5;i++){
//生成链表后续的一个节点
link *a=(link*)malloc(sizeof(link));//动态分配内存给这个新节点,也是链表的后续节点
//对该结点进行赋值
a->elem=i;
a->next=NULL;
//将头节点指向生成结点,再把生成结点赋值为temp
temp->next=a;
temp=a;
}
return p;
}
创建一个不含有头结点的链表
link * initLink(){
link * p=NULL;//创建头指针
link * temp = (link*)malloc(sizeof(link));//创建首元节点
//首元节点先初始化
temp->elem = 1;
temp->next = NULL;
p = temp;//头指针指向首元节点
//从第二个节点开始创建
for (int i=2; i<5; i++) {
//创建一个新节点并初始化
link *a=(link*)malloc(sizeof(link));
a->elem=i;
a->next=NULL;
//将temp节点与新建立的a节点建立逻辑关系
temp->next=a;
//指针temp每次都指向新链表的最后一个节点,其实就是 a节点,这里写temp=a也对
temp=temp->next;
}
//返回建立的节点,只返回头指针 p即可,通过头指针即可找到整个链表
return p;
}
后续插入,删除操作以有头节点为例
插入结点
link *insertElem(link *p,int elem,int add){
link *temp=p;
//插入时候,要考虑到是头插法还是中途插入,尾插法,头插法直接写在头部插入实现,尾插法则找到最后一个下标
//此处循环可以满足三种实现
for(int i=1;inext;
}
link *a=(link*)malloc(sizeof(link));
a->elem=elem;
a->next=temp->next;//插入的节点指向临时定义结点的下一个节点
temp->next=a;
return p;
}
删除节点
link *delElem(link *p,int add){
link *temp = p;//定义一个临时结点,找到要删除的节点
for(int i=1;inext;
}
link *del = temp->next;//定义一个指针指向要被删除的元素
temp->next= temp->next->next;
free(del);//释放资源
return p;
}
查找节点
int selectElem(link *p,int elem){
link *temp=p;
int i=1;
while(temp->next){
temp=temp->next;
if(temp->elem==elem){
return i;
}
i++;
}
return -1;
}
更新节点
link *updateElem(link *p,int add,int elem){
link * temp=p;
temp=temp->next;//在遍历之前,temp指向首元结
for(int i=1;inext;
}
temp->elem=elem;
return p;
}
输出链表的值
void display(link *p){
link* temp=p;//将temp指针重新指向头结点
//只要temp指针指向的结点的next不是Null,就执行输出语句。
while (temp->next) {
temp=temp->next;
printf("%d ",temp->elem);
}
printf("\n");
}
在main函数中调用查看输出即可
typedef struct Link{
int elem;
struct Link *next;
}link;
//初始化节点
link *initLink();
//链表插入函数,p是链表,elem是插入结点的数据域,add是插入位置
link *insertElem(link *p,int elem,int add);
//p是原链表,delete是删除的下标元素
link *delElem(link *p,int add);
//查找元素 ,p是原链表,elem是要查找的元素
int selectElem(link *p,int elem);
//更新元素
link *updateElem(link *p,int add,int elem);
void display(link *p);//打印
int main(){
printf("初始化链表为:\n");
link *p=initLink();
display(p);
printf("在第4的位置插入元素5:\n");
p = insertElem(p,5,4);
display(p);
printf("删除3位置上的元素:\n");
p = delElem(p,3);
display(p);
printf("查找元素2的位置为:\n");
int address=selectElem(p, 2);
if (address==-1) {
printf("没有该元素");
}else{
printf("元素2的位置为:%d\n",address);
}
printf("更改第3的位置上的数据为7:\n");
p=updateElem(p, 3, 7);
display(p);
return 0;
}
link *initLink(){
link *p = (link*)malloc(sizeof(link));//创建一个指针指向头节点
link *temp=p;//声明一个指针指向头结点,用于遍历链表
for(int i=1;i<5;i++){
//生成链表后续的一个节点
link *a=(link*)malloc(sizeof(link));//动态分配内存给这个新节点,也是链表的后续节点
//对该结点进行赋值
a->elem=i;
a->next=NULL;
//将头节点指向生成结点,再把生成结点赋值为temp
temp->next=a;
temp=a;
}
return p;
}
link *insertElem(link *p,int elem,int add){
link *temp=p;
//插入时候,要考虑到是头插法还是中途插入,尾插法,头插法直接写在头部插入实现,尾插法则找到最后一个下标
//此处循环可以满足三种实现
for(int i=1;inext;
}
link *a=(link*)malloc(sizeof(link));
a->elem=elem;
a->next=temp->next;//插入的节点指向临时定义结点的下一个节点
temp->next=a;
return p;
}
link *delElem(link *p,int add){
link *temp = p;//定义一个临时结点,找到要删除的节点
for(int i=1;inext;
}
link *del = temp->next;//定义一个指针指向要被删除的元素
temp->next= temp->next->next;
free(del);//释放资源
return p;
}
int selectElem(link *p,int elem){
link *temp=p;
int i=1;
while(temp->next){
temp=temp->next;
if(temp->elem==elem){
return i;
}
i++;
}
return -1;
}
link *updateElem(link *p,int add,int elem){
link * temp=p;
temp=temp->next;//在遍历之前,temp指向首元结
for(int i=1;inext;
}
temp->elem=elem;
return p;
}
void display(link *p){
link* temp=p;//将temp指针重新指向头结点
//只要temp指针指向的结点的next不是Null,就执行输出语句。
while (temp->next) {
temp=temp->next;
printf("%d ",temp->elem);
}
printf("\n");
}
输出结果如下
如果用JAVA实现如下:
添加节点:
public void addNode(int num){
Node newNode = new Node(num);
if(head == null){
head = newNode;
return;
}
Node node2 = head;
while(node2.next!=null){
node2 = node2.next;
}
node2.next =newNode;
}
删除指定下标的节点
//删除指定下标位置的节点
public void deleteNode(int index){
if(index<0 || index>length()){
System.out.println("index 不准确");
}
if(index == 1){
head = head.next;
}
Node temp = null;
for(int i=1;i
更新节点
//更新结点
public void updateNode(int index,int data){
if(index<0 || index>length()){
System.out.println("index 不准确");
}
if(index == 1){
head = head.next;
}
Node temp = null;
for(int i=1;i
获取长度
//获取长度
public int length(){
int length=0;
Node tmp = head;
while(tmp.next!=null){
length++;
tmp = tmp.next;
}
return length;
}
创建则有所不同,需要add节点进去。(目前没想到怎么一个for循环实现的链表的操作,有知道得请留言讨论)
TestLink list = new TestLink();
list.addNode(5);
list.addNode(3);
list.addNode(1);
list.addNode(2);
list.addNode(55);
list.addNode(36);
System.out.println("linkLength:" + list.length());
System.out.println("head.data:" + list.head.data);
PS:链表涉及到指针,比较抽象,本Wolf还在逐步理解中,此处一篇分享加深下印象,C语言的更适合理解,边画图边实现,我手写了两遍,对语法的不熟悉,还是容易在C创建节点,创建头指针等地方忽略到,思想一定要学到,本篇有错误之处,请及时指出,才发现这种底层的东西,难理解,还费事,主要还是我菜,有不懂的可以加QQ396301446,一起讨论,共同学习进步,选择看数据结构,就要做好被虐准备,我自横刀向天笑,去留肝胆两昆仑。