单链表结构

链表是一种复杂的数据结构,其数据之间的相互关系使链表分成三种:单链表、循环链表、双向链表,下面将逐一介绍。
单链表:单链表有一个头节点head,指向链表在内存的首地址。链表中的每一个节点的数据类型为结构体类型(数据的结构类型,有一个数据域和指针域共同组成了这种新的数据),节点有两个成员:整型成员(实际需要保存的数据,可以是泛型)和指向下一个结构体类型节点的指针即下一个节点的地址。链表按此结构对各节点的访问需从链表的头找起,后续节点的地址由当前节点给出。无论在表中访问那一个节点,都需要从链表的头开始,顺序向后查找。链表的尾节点由于无后续节点,其指针域为空,写作为NULL。
单链表的创建过程:
1、定义一个单链表的数据结构,可以说是一种新的数据类型
2、创建一个空的链表
3、分配一个指针域为NULL,数据域不设置的头结点
4、可以进行链表的使用,加入元素,将新节点的指针域成员赋为NULL,通过循环判断指针域的值是否为NULL,来找到最后一个节点或者是第一个节点
单链表的使用过程:
1、把单链表的头结点传入
2、通过检测节点中的指针域是否非NULL,来循环数据节点中的数据域
3、找到下一个节点的地址,返回第二步

C语言单链表的形式

#include <stdio.h>
#include <stdlib.h>
typedef struct LNode LNode;//新数据类型
typedef struct LNode* pLNode;//指针数据类型
//1、定义结构
struct LNode{
    int data; //数据域
    struct LNode *next; //指针域
};
pLNode addLNode(pLNode p,int i);
pLNode init(pLNode p);
void show(pLNode p);

int main()
{
    pLNode p=NULL; //2、创建空表
    //p=(pLNode)malloc(sizeof(LNode));
    p =init(p);
    int i=0;
    for(i;i<100;i++)
    {
        p = addLNode(p, i);
    }
    show(p);
}
pLNode init(pLNode p)
{
    p=(pLNode)malloc(sizeof(LNode));
    p->next=NULL;
    return p;
}
pLNode addLNode(pLNode p,int i)
{
    pLNode head = p;
    pLNode p1=(pLNode)malloc(sizeof(LNode)); //3、使用malloc函数进行动态内存分配
    p1->next=NULL;                      //4、把新节点的指针域赋值为null
    while(p->next!=NULL){   //并找到最后一个节点或者是第一个节点
        p=p->next;
    }
    p->next=p1;    //5、给最后一个指针域赋值为新创建的节点
    p1->data=i;
          //给最后一个节点赋值
    return head;
}

void show(pLNode p)
{
    while(p->next!=NULL)
    {
        p=p->next;
        printf(" %d  ",p);
        printf("  %d\n",p->data);

    }
}

网上找的一幅图
这里写图片描述

Java语言编写的数据结构

1、第一种实现方式,只有一个头结点和一个节点个数属性,节点内容一样,就是链表的定义方式实现


public class MySigleLinkListe<E> {
    public static class Node<E>{
        Node<E> pNext;
        E item;
        public Node(Node<E> pNext, E item) {
            super();
            this.pNext = pNext;
            this.item = item;
        }
        @Override
        public String toString() {
            return "Node [item=" + item + "]";
        }

    }

    public MySigleLinkListe() {
        super();
        head=new Node<E>(null, null);  //穿件头指针,java中存储方式不一样,
    }

    transient Node<E> head; //头结点
    transient int size; //节点数量

    public void add(E e){
        addLast(e);
    }

    private void addLast(E e){
        final Node<E> n=new Node<E>(null,e);  //新 创建了 一个节点
        Node<E> m=head;   //先把节点引用赋给一个中间变量引用
        if(m.pNext==null) //若果这个节点中的pNext为null,说明是头结点
            m.pNext=n;   //则把新节点引用赋给投头结点中的pNext属性
        else{
            while(m.pNext!=null){ //如果已存在节点,则开始遍历,利用pNext属性为null这一特性来查找最后一个节点,
                m=m.pNext;     //成立,则把当前引用改为 下一个引用,继续遍历
            }
            m.pNext=n; //直到最后一个节点,这时把新节点引用赋给最后一个节点的pNext属性
        } 
        size++;//size++
    }

    public void show(){
        if(head.pNext==null){
            return ;
        }
        Node<E> m=head;
        while(m.pNext!=null){
            m=m.pNext;
            System.out.println(m.item.toString());
        }
    }
    //存储 个数
    public int count(){
        return size;
    }
}

这里写图片描述

使用部分代码

MySigleLinkListe<String> my=new MySigleLinkListe<String>();
        for (int i = 0; i < 10; i++) {
            my.add(i+"");
        }
        my.show();
        System.out.println(my.count());

2、第二种实现方式,使用两个引用,一个头引用,一个尾引用,这样就利于添加


public class MyDoubleLinkList<E> {
    private static class Node<E>{
        E e;
        //Node<E> pFirst;  //头引用
        Node<E> pNext;   //下一个节点的引用
        public Node(E e, Node<E> pNext) {
            super();
            this.e = e;
            this.pNext = pNext;
        }


    }
    transient Node<E> head; //头结点
    transient Node<E> end;  //尾节点
    transient int size;    //节点数
    public MyDoubleLinkList() {
        super();
        head=new Node<E>(null, null);
        end=head;
    }
    public void add(E e){
        addLinkLast(e);
    }
    private void addLinkLast(E e){
        Node<E> n=new Node<E>(e, null);
        Node<E> m=head;
        if(end==head){    //头节点等于尾节点时则说明还没有子节点
            m.pNext=n;   //把头结点的pNext指向下一个节点
            end=n;      //尾节点指向新节点
        }else{
            end.pNext=n;  //把前一次的尾节点保存为新节点
            end=n;     //把新节点保存为尾节点 ,这样可以不用每次插入都找尾节点,效率提升了很多,
        }
        size++;
    }
    public void show(){
        Node<E> m=head;
        while(m.pNext!=null){
            m=m.pNext;
            System.out.println(m.e);
        }
    }
    public E get(int index){
            Node<E> m=head;
            for (int i = 0; i < index+1; i++) {
                m=m.pNext;
            }
            return m.e;
    }
    public int count(){
        return size;
    }
}

使用部分代码

MyDoubleLinkList<String> d=new MyDoubleLinkList<String>();
        for(int j=20;j<30;j++){
            d.add(j+"");
        }
        for(int i=0;i<d.count();i++){
            System.out.print(d.get(i)+"  ");
        }

这里写图片描述
感悟:源码中的linkList实现是基于双端链表的,也就是一个节点既有上一个节点引用也有下一个节点引用,这个思想都是一样的,源码中写的有些很复杂,源码中使用了一个 size属性,就是用来作为一个查找索引使用的,这样有利于查询

这是get(int index)方法的源码一部分
Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)){  //这里直接就进行了 一半处理,这样查找的时间大多会缩短一半,
            Node<E> x = first;     //如果在前一半就从头引用开始查找
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {                  //后一半就从尾引用忘前找,这个实现的前提就是使用双端链表
            Node<E> x = last;    //,才能找到上一个引用
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
单链表转换成双链表通常涉及以下几个步骤: 1. **复制原链表**:首先需要复制原单链表的所有节点,因为双链表每个节点除了存储数据,还需要额外的空间存储指向前一个节点的指针。 2. **修改节点结构**:对于每个复制的节点,将其原有的`next`指针改为`next`和`prev`两个指针,其中`next`指向原链表中的下一个节点,而`prev`设置为当前节点,表示这是一个新的双链表中的节点。 3. **链接新节点**:对于新链表中的第一个节点(即原单链表的第一个节点),其`prev`指针应设为`nullptr`。对于后续的节点,它们的`prev`指针会指向前面的节点。 4. **连接首尾**:最后,将原单链表的头节点的`next`指针指向新链表的头节点,使得原单链表变成双链表的一部分,新链表的最后一个节点的`next`保持不变。 5. **更新长度**:如果有必要,更新双链表的长度信息,因为每个节点都有前后节点。 举个简单的例子,如果你有一个包含`Node`结构(仅包含`data`和`next`)的单链表,你可以定义一个新的`Node`结构,比如`DoublyNode`,添加`prev`字段,然后遍历原来的链表,逐个创建并连接新链表。 注意,这个过程可能会改变原单链表的引用,所以在实际操作中需要注意内存管理和所有权问题。如果你想要保留原单链表,可能需要创建一个新的双链表结构
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值