• 数据结构——链表


    目录

    一、链表概述

    二、模拟实现链表 

    1、结点

             2、遍历链表

    3、获取链表的长度 

    4、添加元素 

    (1)、头插法 

    (2)、尾插法 

    (3)、在指定位置插入元素 

    5、删除元素 

     (1)、删除第一次出现值为key的结点

     (2)、删除所有值为key的结点

    6、清空链表 

    三、常见笔试题 

    1、单链表转置

    2、获取单链表的中间结点 

    3、获取倒数第K个结点

    4、合并两个有序链表 


    一、链表概述

    链表:存储结构上并非连续,元素之间靠指针连接的线性表。

    链表可以分为是否带头节点的链表、是否为循环链表、是否为双向链表,但是不带头的单向链表使用较多,本文也是对不带头的单向链表进行详解。

    二、模拟实现链表 

    1、结点

    链表是有多个结点组成,结点的成员变量有data和next,结点类为链表类的内部类。

    1. class Node{
    2. public int data;
    3. public Node next;
    4. public Node(int data){
    5. this.data=data;
    6. }
    7. }

    2、遍历链表

    定义一个结点指向链表的首元结点,逐个进行遍历,直至链表中某个结点的next为空。

    1. //遍历链表
    2. public void display() {
    3. if(head==null){
    4. return;
    5. }
    6. Node cur=head;
    7. while(cur!=null){
    8. System.out.print(cur.data+" ");
    9. cur=cur.next;
    10. }
    11. }

    3、获取链表的长度 

    与遍历链表相似,只是需要设置一个计数器,来求出链表的长度。

    1. public int size() {
    2. int count=0;
    3. Node cur = head;
    4. while(cur!=null){
    5. count++;
    6. cur=cur.next;
    7. }
    8. return count;
    9. }

    4、添加元素 

    (1)、头插法 

    将待添加的元素插入到链表首部,需要将待插的元素封装为一个结点,将该结点的next指向链表的首部,再将链表的首部指向该结点,那么该结点为链表的首元结点。

    1. //头插法
    2. public void addFirst(int data) {
    3. Node node=new Node(data);
    4. node.next=head;
    5. head=node;
    6. }

    (2)、尾插法 

    将需要插入的元素封装为一个结点,遍历整个链表,让链表的最后一个结点的next指向新增的结点即可。

      

    1. //尾插法
    2. public void addLast(int data) {
    3. Node node=new Node(data);
    4. if(head==null){
    5. head=node;
    6. return;
    7. }
    8. Node cur=head;
    9. while(cur.next!=null){
    10. cur=cur.next;
    11. }
    12. cur.next=node;
    13. }

    (3)、在指定位置插入元素 

    可以再定义一个方法获取指定位置的结点,以此得到指定位置的前一个结点,然后新增结点的next为前一个结点的next, 前一个结点的next为新增结点。

     

    1. //获取指定位置的结点
    2. public Node indexNode(int index){
    3. if(index<0||index>size()){
    4. throw new PosException("位置不合法!");
    5. }
    6. int count=0;
    7. Node cur = head;
    8. while(count!=index){
    9. cur=cur.next;
    10. count++;
    11. }
    12. return cur;
    13. }
    14. //任意位置插入,第一个数据节点为0号下标
    15. public boolean addIndex(int index, int data) {
    16. if(index==0){
    17. addFirst(data);
    18. return true;
    19. }
    20. if(index==size()){
    21. addLast(data);
    22. return true;
    23. }
    24. if(index<0||index>size()){
    25. throw new PosException("位置不合法!");
    26. }
    27. Node cur=indexNode(index-1);
    28. Node node=new Node(data);
    29. node.next=cur.next;
    30. cur.next=node;
    31. return true;
    32. }

    5、删除元素 

     (1)、删除第一次出现值为key的结点

    首先对特殊情况(链表为空,链表的首元结点为要删除的节点,直接将首元结点指向其下一个结点) 处理,然后找到key结点的前一个结点,让其的next指向next.next。

     

    1. //删除第一次出现关键字为key的节点
    2. public void remove(int key) {
    3. if(head==null){
    4. return;
    5. }
    6. Node cur=head;
    7. if(cur.data==key&&cur.next==null){
    8. head=null;
    9. return;
    10. }
    11. while(cur.next!=null){
    12. if(cur.next.data==key){
    13. cur.next=cur.next.next;
    14. return;
    15. }
    16. cur=cur.next;
    17. }
    18. System.out.println("链表中不存在"+key+"元素");
    19. }

    (2)、删除所有值为key的结点

    首先仍需对特殊情况进行处理(与上述类似),采用双指针,开始时,pre和cur都指向首元结点, 当cur继续像后遍历时,若值为key,则pre.next=cur.next,否则pre=cur。

      

    1. //删除所有值为key的结点
    2. public void removeAllKey(int key) {
    3. if(head==null){
    4. return;
    5. }
    6. while(head.data==key){
    7. if(head.next==null){
    8. head=null;
    9. }
    10. head=head.next;
    11. }
    12. Node pre=head;
    13. Node cur=head.next;
    14. while(cur!=null){
    15. if(cur.data==key){
    16. pre.next=cur.next;
    17. cur=cur.next;
    18. }else{
    19. pre=cur;
    20. cur=cur.next;
    21. }
    22. }
    23. /*if(head.data==key){
    24. if(head.next==null){
    25. head=null;
    26. }
    27. head=head.next;
    28. }*/
    29. }

    6、清空链表 

    直接将首元结点置为null即可。

    1. public void clear() {
    2. head=null;
    3. }

    三、常见笔试题 

     1、单链表转置

    对链表为空和链表只有一个首元结点进行处理,定义一个结点cur指向首元结点的next,然后利用头插法的思想,将所有的元素进行转置。

    1. public ListNode reverseList(ListNode head) {
    2. if(head==null){
    3. return null;
    4. }
    5. if(head.next==null){
    6. return head;
    7. }
    8. ListNode cur=head.next;
    9. head.next=null;
    10. while(cur!=null){
    11. ListNode node=cur.next;
    12. cur.next=head;
    13. head=cur;
    14. cur=node;
    15. }
    16. return head;
    17. }

    2、获取单链表的中间结点 

    同样需要对特殊情况进行处理,之后不再赘述,利用快慢指针的思想,在进行遍历时,slow向后走一步,fast向后走两步,这里需要考虑到链表长度为奇数和偶数的情况,对于奇数的

    情况是fast.next==null,对于偶数的情况是fast==null,故将while的循环条件写为(fast!=null&&fast.next!=null)。

     

     

    1. public ListNode middleNode(ListNode head) {
    2. if(head==null){
    3. return null;
    4. }
    5. if(head.next==null){
    6. return head;
    7. }
    8. ListNode slow=head.next;
    9. ListNode fast=head.next.next;
    10. while(fast!=null&&fast.next!=null){
    11. slow=slow.next;
    12. fast=fast.next.next;
    13. }
    14. return slow;
    15. }

    3、获取倒数第K个结点

    依旧采用快慢指针的思想,首先对k的合法性进行判断,先将fast和slow都指向head,然后将fast指向其后的k个结点,最后进行while循环,fast和slow继续向后进行遍历,循环条件为fast!=null,循环结束后slow指向倒数第k个结点。

     

    1. public ListNode FindKthToTail(ListNode head,int k) {
    2. if(k<=0){
    3. return null;
    4. }
    5. if(head==null){
    6. return null;
    7. }
    8. ListNode slow=head;
    9. ListNode fast=head;
    10. int count=0;
    11. while(count<k){
    12. if(fast==null){
    13. return null;
    14. }
    15. fast=fast.next;
    16. count++;
    17. }
    18. while(fast!=null){
    19. slow=slow.next;
    20. fast=fast.next;
    21. }
    22. return slow;
    23. }

    4、合并两个有序链表 

    需要定义一个新的首元结点,再定义一个结点cur指向该结点然后对两个链表进行遍历,循环条件为两个链表不为空,较小的结点添加到cur结点之后,遍历完成后,若还有链表没有遍历完,则cur.next指向未遍历完链表的首元结点。最后将新链表的首元结点进行删除。

    1. public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
    2. ListNode newHead=new ListNode(0);
    3. ListNode cur=newHead;
    4. while(list1!=null&&list2!=null){
    5. if(list1.val<list2.val){
    6. cur.next=list1;
    7. list1=list1.next;
    8. }else{
    9. cur.next=list2;
    10. list2=list2.next;
    11. }
    12. cur=cur.next;
    13. }
    14. if(list1!=null){
    15. cur.next=list1;
    16. }
    17. if(list2!=null){
    18. cur.next=list2;
    19. }
    20. newHead=newHead.next;
    21. return newHead;
    22. }

     5、判断链表是否为回文

    首先需要找到中间结点,然后从中间结点的后一个改变指向,进行翻转,最后进行遍历。

     

    1. public boolean chkPalindrome(ListNode head) {
    2. if(head==null){
    3. return false;
    4. }
    5. if(head.next==null){
    6. return true;
    7. }
    8. ListNode slow=head;
    9. ListNode fast=head;
    10. //1、寻找中间结点
    11. while(fast!=null&&fast.next!=null){
    12. fast=fast.next.next;
    13. slow=slow.next;
    14. }
    15. //2.翻转
    16. ListNode cur=slow.next;
    17. while(cur!=null){
    18. ListNode curNext=cur.next;
    19. cur.next=slow;
    20. slow=cur;//注意:此处不能写slow=slow.next,因为slow后面结点的next指向已发生改变
    21. cur=curNext;
    22. }
    23. //3.判断
    24. while(head!=slow){
    25. if(head.val!=slow.val){
    26. return false;
    27. }
    28. if(head.next==slow){
    29. return true;
    30. }
    31. head=head.next;
    32. slow=slow.next;
    33. }
    34. return true;
    35. }

     

     

     

     

     

     

     

     

     

     

     

  • 相关阅读:
    图扑软件 3D 组态编辑器,低代码零代码构建数字孪生工厂
    Amazon云计算AWS之[1]基础存储架构Dynamo
    【STM32 HAL】串口中断控制相关
    ABAP基础知识 数据读取的缓存
    软件概要设计-架构真题(二十五)
    聚类的方法、原理以及一般过程
    【Nginx】Windows平台下配置Nginx服务实现负载均衡
    Go :复制预声明功能的半穷举测试(附完整源码)
    Spring知识点讲解 【笔记】
    ansible
  • 原文地址:https://blog.csdn.net/m0_62404808/article/details/128161510