无头单向链表的实现

链表

链表的结构非常像火车,有车厢、有连接车厢的挂钩

车厢里存储的数据,每个车厢都有座位,还有有一个挂钩,用于连接另一节车厢,而这些车厢连在一起就是一整个火车

同样的,对于链表来说,存在若干个结点Node,结点中存储这数据和下个结点的地址,将这些Node连接在一起的整体就是链表

现在要来设置一下Node:

class Node{
  int data;//以int型为例
  Node next;
}

车厢 - Node已经有了,而对于用户来说,用户在意的并不是某一节车厢,而是哪一个车次,因此我们引入一个头结点,通过头结点就能过找到后续所有的结点

用户只需要调用单链表提供的增删改查方法即可,不需要关心链表内部是如何实现的,就像坐火车,你只管入座,火车怎么驾驶,车厢之间怎么连接都不需要关心,更不需要你去做

根据上面两点,就可以定义链表:

public class SingleLinkedList{
    Node head;//头结点 - 有了头结点就能通过头结点找到整个链表
    int size;//记录元素个数

    public String toString(){
        int cur = head;
        String ret = "";
        while(cur != null){
            ret += cur.data;
            ret += "->";
            cur = cur.next;
        }
        ret += "NULL";
    }

}

增加

void addFirst(int val) -> 在链表头部插入结点

void add(int index, int val) -> 在链表索引为index处插入元素值为val的结点

void addLast(int val) -> 在链表尾部插入结点

除了链表的头结点,其他结点都是中间位置,包括尾结点,因此处理头结点的时候要单独处理

因为只有头结点没有前驱,其他的结点都有前驱结点,所以找到前驱结点至关重要

//头插法
public void addFirst(int val){
    Node node = new Node(val);
    if(head != null){
        node.next = head;
    }
    head = node;
    size++;
}

//在index处插入
public void addIndex(int index, int val){
    if(index < 0 || index > size){
        System.out.println("add index is illegal");
        return;
    }
    //头结点没有前驱,因此需要特殊处理
    if(index == 0){
        addFirst(val);
        return;
    }
    //在单链表中最核心的部分就是找到前驱,因为我们需把新结点的地址赋值给前驱的next属性
    Node prev = head;
  	//从头结点开始,找到索引为index的结点的前驱,需要走index - 1步
    for (int i = 0; i < index - 1; i++) {
        prev = prev.next;
    }
    Node node = new Node(val);
  	//下面两步的操作顺序不能变,否则node.next就指向自己了,而后面的结点将丢失
    node.next = prev.next;
    prev.next = node;
    size++;
}

//尾插法
public void addLast(int val){
  	//调用我们写好的代码即可
    addIndex(size, val);//size正好是我们需要插入的位置
}

我们观察一下链表和顺序表的add方法

如果把动态数组换成链表,对于用户来说,是非常容易的,只需要更换类即可,调用的方法,和使用的规则完全一致

而这就是面向对象开发的魅力所在

查找

int getByValue(int val) -> 查找索引为index的元素值是多少

boolean contains(int val) -> 查询是否包含元素值为val的结点

int getByIndex(int index) -> 查找链表中第一个值为val的结点索引是多少

实际上,不管是查找、修改还是删除,都需要判断index的合法性,且这三种数据操作的index的取值都在**[ 0, size )**之间

既然如此,我们直接将index的检验抽象成一个方法,并且定义成私有方法,只供程序内部自己使用的检验方法,用户不需要调用

//在链表中找到值为val的元素,并返回下标,否则返回-1
public int getByval(int val){
    Node node = head;
    int num = 0;
    while(node != null){
        if(node.data == val){
            return num;
        }
        num++;
        node = node.next;
    }
    return -1;
}
//第二种写法
public int getByval(int val){
  	int index = 0;
    for (Node node = head; node != null; i++) {
        if(node.data == val){
            return i;
        }
        node = node.next;
    }
    return -1;
}

//查找链表中是否包含val
public boolean contains(int val){
    return getByval(val) != -1;
}

//查找链表中,下标为index的元素值
public int getByIndex(int index){
    if(!indexLegal(index)){
        System.out.println("get index is illegal");
        return -1;
    }
    Node node = head;
    for (int i = 0; i < index; i++) {
        node = node.next;
    }
    return node.data;
}

修改

int sex(int index, int newVal) -> 在索引值为index处插入元素值newVal,并返回修改前的元素

//将下标为index的结点的值修改为newVal,并返回旧的值
public int set(int index, int newVal){
    if(!indexLegal(index)){
        System.out.println("set index is illegal");
        return -1;
    }
    Node node = head;
    for (int i = 0; i < index; i++) {
        node = node.next;
    }
    int oldVal = node.data;
    node.data = newVal;
    return oldVal;
}

删除

remove(int index) -> 删除索引为index位置的元素

removeValueOnce(int val) -> 删除第一个值为val的元素

removeAllValue(int val) -> 删除链表中所有值为val的元素

删除元素值为val的结点的时候,尤其要注意我们要找到的是待删除结点的前驱

//删除索引为index位置的元素,返回被删除的元素值
public int remove(int index){
    if(!indexLegal(index)){
        System.out.println("remove index illegal");
        return -1;
    }
  	//头结点没有前驱,需要特殊处理
    if(index == 0){
        Node prev = head;
        head = head.next;
        prev.next = null;
        size--;
        return prev.data;
    } else {
        Node prev = head;
        for (int i = 0; i < index - 1; i++) {
            prev = prev.next;
        }
        Node node = prev.next;
        prev.next = node.next;
        node.next = null;
        size--;
        return node.data;
    }
}

//删除链表中第一个值为val的节点,返回是否删除成功
public void removeValOnce(int val){
    if(head == null){
        System.out.println("LinkedList is empty");
    }
    //头结点就是待删除元素
    if(head.data == val){
        Node x = head;
        head = x.next;
        x.next = null;
        size--;
        return;
    }
    Node prev = head;
  	//因为prev = head,所以此时的prev一定不是待删除结点,如果是的话,在上面的代码中就已经处理过了
  	//因此,我们判断结点是否等于待删除结点,至少也要从当前结点的下一个结点开始判断,即prev.next.data
  	//那么为了保证data的存在,我们就要保证prev.next的存在,因此whil的循环条件就是prev.next != null
    while(prev.next != null){
        if(prev.next.data == val){
            Node x = prev.next;
            prev.next = x.next;
            x.next = null;
        }
        prev = prev.next;
    }
    System.out.println("val is not existing");
}

//删除链表中所有值为val的结点
public void removeAllVal(int val){
    //如果头结点是待删除元素,新的头结点可能还是待删除元素,因此要用while循环
  	//其次,如果整个链表都是待删除结点,head最终会取null,如果继续运行会报空指针异常
    while(head != null && head.data == val){
        Node x = head;
        head = x.next;
        x.next = null;
        size--;
    }
    //如果整个链表都已经删完了,直接退出
    if(head == null){
        return;
    }
    Node prev = head;
    while(prev.next != null){
        if(prev.next.data == val){
            Node x = prev.next;
            prev.next = x.next;
            x.next = null;
            size--;
        }else{
            prev = prev.next;
        }
    }
}

//删除头结点
public void removeFirst(){
    remove(0);
}

//删除尾结点
public void removeLast(){
    remove(size - 1);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当我们需要在单向无头链表中查找某个元素时,我们需要遍历整个链表,直到找到目标元素或者链表结束。下面是一个示例的单向无头链表查找代码: ```c++ #include <iostream> // 定义链表节点结构 struct Node { int data; // 节点数据 Node* next; // 指向下一个节点的指针 }; // 在链表尾部插入新节点 void insert(Node** head, int data) { Node* newNode = new Node(); // 创建新节点 newNode->data = data; newNode->next = nullptr; if (*head == nullptr) { *head = newNode; } else { Node* currNode = *head; while (currNode->next != nullptr) { currNode = currNode->next; } currNode->next = newNode; } } // 在链表中查找目标元素 bool search(Node* head, int target) { Node* currNode = head; while (currNode != nullptr) { if (currNode->data == target) { return true; } currNode = currNode->next; } return false; } // 打印链表元素 void display(Node* head) { Node* currNode = head; while (currNode != nullptr) { std::cout << currNode->data << " "; currNode = currNode->next; } std::cout << std::endl; } int main() { Node* head = nullptr; // 插入节点 insert(&head, 1); insert(&head, 2); insert(&head, 3); insert(&head, 4); // 打印链表 display(head); // 查找元素 int target = 3; if (search(head, target)) { std::cout << target << " found in the list." << std::endl; } else { std::cout << target << " not found in the list." << std::endl; } return 0; } ``` 在上面的代码中,我们定义了一个 `Node` 结构表示链表的节点,其中 `data` 存储节点的数据,`next` 指向下一个节点。`insert` 函数用于在链表尾部插入新节点。`search` 函数用于在链表中查找目标元素,并返回是否找到。`display` 函数用于打印链表的所有元素。 在 `main` 函数中,我们创建一个空链表,并插入一些节点。然后,我们调用 `display` 函数打印链表,再调用 `search` 函数查找目标元素是否存在。最后输出结果。 注意:这只是一个简单的示例代码,实际应用中可能需要处理更多的边界情况和错误处理。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值