03-SinglyLinkedList-单链表(C/Java)

单向链表

1.概述

为了表示每个数据元素a与其直接后继数据元素ai+1之间的逻辑关系,对数据元素a来说除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称做指针或链。这两部分信息组成数据元素ai的存储映像,称为结点(Node)。

2.组成

头指针(哨兵节点):链表指向第一个节点的指针,若有头节点则指向头节点(必要)

头节点:为统一操作方便而设立,放在第一个元素的节点之前,其数据域一般无意义(非必要)

尾节点:线性链表的最后一个节点指针指向为NULL

image-20230903172719963

3.分类

image-20230905103707761

4.代码实现

Java实现方式

1)创建初始节点
private Node head = null;  //头指针:方便处理端值
private static class Node {
	int value;  //节点的值
    Node next;  //指向下一个节点的指针
    public Node(int value, Node next) {
        this.value = value;
        this.next = next;
    }
 }
2)插入操作
  • 头插法
public void addFirst(int value){
        /** 分析
         * //1.链表为空链表
         * head = new Node(value, null);
         * //2.链表不为空
         * head = new Node(value, head.next);
         */
        //合并情况处理
        head = new Node(value, head);
    }

注意:

head = new Node()也可写成head.next = new Node();

head = new Node(value, head.next)head.next = new Node(value, head.next) 在功能上是相同的,都是用于在链表的头部插入一个新的节点。

区别在于赋值的逻辑,head = new Node(value, head.next) 是将变量 head 直接指向新的节点,将新的节点设置为链表的新头节点。

head.next = new Node(value, head.next) 是将原先的头节点的下一个节点指向新的节点,新节点成为链表的新头节点,但是 head 的值保持不变。

所以,两者的结果相同,都能实现在链表头部插入新节点的操作,只是赋值的逻辑略有不同。

  • 尾插法
public void addLast(int value){
        Node p = head;
        //1.如果是空链表,可以直接插入节点
        if (p == null) {
            addFirst(value);
            return;
        }
        //2.如果不是空链表,先进行遍历,找到末尾
        while (p.next != null){
            p = p.next;
        }
        p.next = new Node(value, null);
    }
  • 任意位置插入
/**
     * 根据索引值插入值
     * @param index
     * @param value
     */
    public void insert(int index, int value) {
        //对索引第一个位置的节点插入
        if (index == 0) {
            addFirst(value);
            return;
        }
        Node p = getNode(index - 1);
        //检查索引是否合法
        if (p == null){ throw new IllegalArgumentException(String.format("索引不合法"));}
        //插入操作
        p.next = new Node(value, p.next.next);

    }
    //查找元素
    private Node getNode(int index) {
        //遍历索引
        Node p = head;
        for (int i = 0; p != null; p = p.next, i++) {
            if (i == index) break;//找到索引前一个位置的节点
        }
        return p;
    }
3)查找元素
 private Node getNode(int index) {
        //遍历索引
        Node p = head;
        for (int i = 0; p != null; p = p.next, i++) {
            if (i == index) break;//找到索引前一个位置的节点
        }
        return p;
    }
4)遍历链表
   //遍历链表
    /**
     * 循环遍历
     * @param consumer
     */
    public void loop1(Consumer<Integer> consumer){
        Node p = head;//创建一个遍历元素的指针
        while (p != null){//当链表不为空的情况下进行遍历
            //这里用consumer去接收参数,将获得的元素的使用方法交给用户
            consumer.accept(p.value);
            p = p.next;
        }
    }

    /**
     * 迭代器实现元素的遍历
     * @param
     */
    @Override
    public Iterator<Integer> iterator() {

        return new Iterator<Integer>() {
            Node p = head;
            @Override
            public boolean hasNext() {//是否存在下一个元素
                return p != null;
            }

            @Override
            public Integer next() {//返回当前值,并指向下一个元素
                int v = p.value;
                p = p.next;
                return v;
            }
        };
    }

    //根据索引获取元素的值
    public int getElem(int index){
        //1.遍历链表,当到index时返回其值
        int i = 0;//用于记录遍历的次序
        for (Node p = head; p != null; p = p.next, i++){
            if (i == index){
                return p.value;
            }
        }
        //2.没有找到抛异常
        throw new IllegalArgumentException(String.format("索引不合法"));
    }

注意:

对于内部类是否加static的经验:如果在调用的方法中,方法内部没有全局变量,则不可以加static,否则可以加。

在Java中,static关键字用于声明静态成员,即与特定对象实例无关的成员。以下是在Java中需要使用static的一些情况:

静态成员变量:当一个成员变量需要在多个对象之间共享,或者希望在没有创建对象实例的情况下访问成员变量时,可将其声明为静态。

class MyClass {
    static int count; // 静态成员变量
}

静态方法:当一个方法不需要访问和操作特定对象的状态,即不依赖于对象的实例变量,可将其声明为静态方法。

class MyClass {
    static void printHello() { // 静态方法
        System.out.println("Hello");
    }
}

静态代码块:静态代码块在类加载时执行,并且只执行一次,可用于初始化静态变量或执行其他必要的静态操作。

class MyClass {
    static {
        // 静态代码块
        // 执行一些初始化操作
    }
}

静态内部类:当一个内部类不需要访问外部类的实例变量或方法时,可将其声明为静态内部类。

class OuterClass {
    static class InnerClass { // 静态内部类
        // ...
    }
}

需要注意的是,静态成员可以通过类名直接访问,而不需要创建对象实例。但是,静态成员不能直接访问非静态成员,因为非静态成员依赖于对象实例的存在。

5)获取元素
/**
     * 获取对于索引中链表的元素
     * @param index
     * @return
     */
    public int getElem(int index){
        //1.遍历链表,当到index时返回其值
        int i = 0;//用于记录遍历的次序
        for (Node p = head; p != null; p = p.next, i++){
            if (i == index){
                return p.value;
            }
        }
        //2.没有找到抛异常
        throw new IllegalArgumentException("索引不合法"));
    }
6)删除元素
/**
     * 根据索引值删除链表节点的元素
     * @param index
     */
    public void remove(int index){
        //删除第一个元素
        if (head == null){ return;}//链表为空则不予进行删除
        if (index == 0){ head = head.next;return;}
        //遍历链表查找元素
        Node prev = getNode(index - 1);
        Node removed = prev.next;
        //判断索引位置是否合法:即索引是否越界
        //1.删除元素刚好为末尾后的的第一个元素
        if (prev == null){ throw new IllegalArgumentException("索引不合法");}
        //2.删除元素越界且不为为末尾后的的第一个元素
        if (removed == null){ throw new IllegalArgumentException("索引不合法");}
        //删除元素
        prev.next = removed.next;
    }

C实现方法

1)创建单向链表:
typedef int ElemType;//方便更改数据类型 
/*线性表的单链表存储结构*/
typedef struct Node{
	ElemType data;
	struct Node *next;
} Node;
typedef struct Node *LinkList;//创建链表LinkList
2)单链表的取值:
Status GetElem(LinkList L, int i, ElemType *e){
	int j = 1;
	LinkList p;
	p = L->next;	//让p指向链表第一个节点 
	while(p && j<i){//p不为空或者j还没有等于i 
		p = p->next;
		++j;
	}	
	if(!p || j>i) return ERROR;//第一个元素不存在 
	*e = p->data;
	return OK;
}
3)单链表插入元素:
Status ListInsert(LinkList *L, int i, ElemType e){
	//1.查找新元素
	int j = 1;
	LinkList p;
	p = *L;
	//2.先判断是否存在第一个节点
	if(!p || j > i) return ERROR; 
	//3.寻找节点并插入
	while(p && j < i){
		p = p->next;
		j++;
	} 
	//4.创建新节点
	LinkList s = (LinkList) malloc(sizeof(Node)); 
	
	s->data = e;
	s->next = p->next;
	p->next = s;
	return OK;	 
}
4)单链表删除元素:
Status ListDelete(LinkList *L, int i, ElemType *e){
	//1.查找新元素
	int j = 1;
	LinkList p, q;
	p = *L;
	//2.先判断是否存在第一个节点
	if(!p || j > i || !(p->next)) return ERROR;
	//3.查找元素
	while(p && j < i){
		p = p->next;
		j++;
	} 
	q = p->next;
	p->next = q->next;
	*e = q->data;
	//4.让系统回收此节点,释放内存 
	free(q);
	return OK; 
}
5)结构体知识回顾:

1.结构体的声明:

struct 结构体名(也就是可选标记名)
{
    成员变量;
}//使用分号表示定义结束;
//此时的结构体名相当于int,float,即自定义类型

2.结构体变量名

方法一:
struct 结构体名{
...
}变量名;

方法二:
struct 结构体名 变量名;
    
方法三:
typedef struct{
    
}结构体名;

3.结构体初始化

1.可以直接在创建结构体时赋值
struct book s1={//对结构体初始化 
        "yuwen",//title为字符串 
        "guojiajiaoyun",//author为字符数组 
        22.5    //value为flaot型 
    };
//要对应起来,用逗号分隔开来,与数组初始化一样;

2.如果初始化之后,就不可以直接赋值
book.title = ""
book -> data = 1 //指针赋值

4.malloc()函数和free()函数;
malloc()函数的作用就是动态分配内存;
介绍:
malloc 向系统申请分配指定size个字节的内存空间。返回类型是 void* 类型。void* 表示未确定类型的指针。C,C++规定,void* 类型可以通过类型转换强制转换为任何其它类型的指针;
头文件为;#include<stdlib.h>

总结;

1;malloc只分配内存不初始化值,
2;注意malloc分配内存后,要来个判断是否成功分配;
3;malloc返回的指针需要强制转化;
4;malloc和free是配对使用的;
5;free之后,要将那指针指向NULL;
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值