c++ 链表_算法闯关之数组与链表

78a4f58dde66dfe43e12203336bba2c4.png

数组

什么是数组

数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。 - 线性表就是数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后两个方向。其实除了数组,链表、队列、栈等也是线性表结构。而与它相对立的概念是非线性表,比如二叉树、堆、图等。之所以叫非线性,是因为,在非线性表中,数据之间并不是简单的前后关系。

面试问题:数组与链表主要区别 链表适合插入、删除,时间复杂度是O(1),而数组支持随机访问,根据下表随机访问的时间复杂度为O(1);

链表

什么是链表

数组需要连续的储存空间,而链表不需要连续的存储空间,它是通过指针将一组连续的内存块串联起来, 比如申请的是 100MB 大小的链表,不会有问题。这里把内存块称为节点,它不仅要储存数据,而且需要记录下一个节点的地址,把记录下一个节点地址称为后继指针next。 链表第一个节点称为头结点,最后一个节点称为尾节点,头结点用于记录链表的基地址,就可以遍历整个链表,而尾节点不是指向下一个节点,而是指向空地址NUll。 并且链表插入、删除时间复杂度为O(1),而随机访问时间复杂度是O(n)。

循环链表与双向链表

  • 循环链表是一种特殊的单链表。它跟单链表唯一的区别就在尾结点。单链表的尾结点指针指向空地址,表示这就是最后的结点了,但是循环链表的尾结点指针是指向链表的头结点;
  • 双向链表:单向链表只有一个方向,结点只有一个后继指针next 指向后面的结点。而双向链表,它支持两个方向,每个结点不止有一个后继指针 next 指向后面的结点,还有一个前驱指针 prev 指向前面的结点;
  • 如果存储相同的数据,双向链表需要更多的存储空间,但它支持双向遍历,操作灵活;
  • 虽然单链表删除时间复杂度为O(1),但每次删除都需要遍历整个链表,所以时间复杂度为O(n),而双向链表已经保存前驱节点,所以遍历时间复杂度为O(1)。Java中的LinkedHashMap就采用双向链表数据结构

数组与链表区别

  • 数组简单易用,在实现上使用的是连续的内存空间,可以借助 CPU 的缓存机制,预读数组中的数据,所以访问效率更高。而链表在内存中并不是连续存储,所以对 CPU 缓存不友好,没办法有效预读;
  • 数组的缺点是大小固定:一经声明就要占用整块连续内存空间。如果声明的数组过大,系统可能没有足够的连续内存空间分配给它,导致内存不足出现(out of memory)。如果声明的数组过小,则可能出现不够用的情况。这时只能再申请一个更大的内存空间,把原数组拷贝进去,非常费时;
  • 链表本身没有大小的限制,并且支持动态扩容;

单链表操作

反转

方法一:递归反转法,在反转当前节点之前先反转后续节点。这样从头结点开始,层层深入直到尾结点才开始反转指针域的指向。简单的说就是从尾结点开始,逆向反转各个结点的指针域指向。

public class SimpleLinkList {
 public static void main(String[] args) {
  Node head = new Node(0);
  Node node1 = new Node(1);
  Node node2 = new Node(2);
  Node node3 = new Node(3);
  head.setNext(node1);
  node1.setNext(node2);
  node2.setNext(node3);

  // 打印反转前的链表,定义一个h保存head节点数据,否则打印代码执行完毕head就为空了,下面反转代码就无法执行
  Node h = head;
  while (null != h) {
   System.out.print(h.getData() + " ");
   h = h.getNext();
  }
  // 调用反转方法
  head = Reverse1(head);
  System.out.println("n**************************");
  // 打印反转后的结果
  while (null != head) {
   System.out.print(head.getData() + " ");
   head = head.getNext();
  }
 }
 /**
  * 递归,在反转当前节点之前先反转后续节点
  */
 public static Node Reverse1(Node head) {
  // head看作是前一结点,head.getNext()是当前结点,reHead是反转后新链表的头结点
  if (head == null || head.getNext() == null) {
   return head;// 若为空链或者当前结点在尾结点,则直接返回
  }
  Node reHead = Reverse1(head.getNext());// 先反转后续节点head.getNext()

  head.getNext().setNext(head);// 将当前结点的指针域指向前一结点

  head.setNext(null);// 前一结点的指针域令为null;

  return reHead;// 反转后新链表的头结点
 }
}
 class Node {

  private int Data;// 数据域
  private Node Next;// 指针域

  public Node(int Data) {
   this.Data = Data;
  }
  public int getData() {
   return Data;
  }

  public void setData(int Data) {
   this.Data = Data;
  }

  public Node getNext() {
   return Next;
  }

  public void setNext(Node Next) {
   this.Next = Next;
  }
 }

方法二:

public static Node reverse(Node list) {
    Node curr = list, pre = null;
    while (curr != null) {
      Node next = curr.next;
      curr.next = pre;
      pre = curr;
      curr = next;
    }
    return pre;
  }

检查是否有环

例如这样的链表:A->B->C->D->B->C->D, 当遍历到节点D的时候,我们需要比较的是之前的节点A、B、C,不存在相同节点。这时候要遍历的下一个新节点是B,B之前的节点A、B、C、D中恰好也存在B,因此B出现了两次,判断出链表有环。 采取了快慢指针:

public static boolean checkCircle(Node list){
        if(list==null) return false;
        Node fastNode=list.next;
        Node slowNode = list;
        while(fastNode!=null&&fastNode.next!=null){
            fastNode = fastNode.next.next;
            slowNode = slowNode.next;
            if(fastNode==slowNode) return true;
        }
        return false;
    }

求单链表中间节点

public static Node findMiddleNode(Node list){
         if (list == null) return null;

         //fast 节点存储着list
            Node fast = list;
            Node slow = list;

            while (fast.next != null && fast.next.next != null) {
                //fast节点存储着fast下下节点内存地址
              fast = fast.next.next;
              slow = slow.next;
            }

            return slow;
    }

有序单链表合并

public static Node mergeSortNode(Node la,Node lb){
        if(la==null) return lb;
        if(lb==null) return la;
        Node pNode = la;
        Node qNode = lb;
        Node head;
        if(pNode.data<qNode.data){
            head = pNode;
            pNode = pNode.next;
        }else {
            head = qNode;
            qNode = qNode.next;
        }
        Node rNode= head;
        while(pNode!=null&&qNode!=null){
            if(pNode.data<qNode.data){
                rNode.next = pNode;
                pNode = pNode.next;
            }else {
                rNode.next = qNode;
                qNode = qNode.next;
            }
            rNode = rNode.next;
        }
        if(pNode!=null){
            rNode.next =pNode;
        }else {
            rNode.next = qNode;
        }
        return head;
    }

删除倒数第k个节点

java public static Node deleteLastNode(Node list, int k){ Node fast = list; int i=1; while(fast!=null&&i<k){ fast = fast.next; ++i; } if(fast==null) return null; Node slow = list; Node preve = null; while(fast.next!=null){ fast = fast.next; preve = slow; slow = slow.next; } if(preve==null){ list = list.next; }else { preve.next = preve.next.next; } return list; }

单链表插入与删除

/**
 * 单链表插入与删除
 * @author zhangtianzhu
 *
 */
public class SingleListDemo {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        SingleListDemo sDemo = new SingleListDemo();
        Node head = null;
        //新增结点,第一个新增需要返回头指针
        head = sDemo.insertNode(1, head);
        sDemo.insertNode(2, head);
        sDemo.insertNode(3, head);
        sDemo.printListNode(head);
        System.out.println(sDemo.getNodeLength(head));
        //删除索引为2的节点
        if((head = sDemo.deleteNode(2, head))!=null){
            sDemo.printListNode(head);
        }

        //删除头结点
        if((head = sDemo.deleteNode(1, head))!=null){
            sDemo.printListNode(head);
        }
    }

    /**
     * 插入节点
     * @param data
     * @param head
     * @return
     */
    public Node insertNode(int data,Node head){
        Node node = new Node(data);
        if(head==null){
            head = node;
            return head;
        }
        Node curNode = head;
        //循环找到当前尾结点
        while(curNode.next!=null){
            curNode = curNode.next;
        }
        //尾结点指针指向新增结点
        curNode.next = node;
        return head;
    }

    /**
     * 单链表长度
     * @param head
     * @return
     */
    public int getNodeLength(Node head){
        Node node = head;
        int length = 0;
        while(node!=null){
            node = node.next;
            length++;
        }
        return length;
    }

    /**
     * 删除结点
     * @param index
     * @param head
     * @return
     */
    public Node deleteNode(int index,Node head){
        if(index<1||index>getNodeLength(head)){
            return null;
        }
        //删除头结点
        if(index==1){
            head = head.next;
            return head;
        }

        //从头结点下一个结点遍历
        Node curNode = head.next;
        //记录当前循环上一个节点用于删除结点
        Node preNode = head;
        int i = 2;
        while(curNode.next!=null){
            if(i==index){
                //删除结点,前一个结点指向当前删除的下一个结点
                preNode.next = curNode.next;
            }else {
                preNode = curNode;
                curNode = curNode.next;
            }
            i++;
        }
        return head;
    }

    public void printListNode(Node head){
        Node curNode = head;
        while(curNode!=null){
            System.out.print(curNode.getData()+" ");
            curNode = curNode.next;
        }
        System.out.println();
    }

}

class Node{
    int data;
    Node next = null;

    public Node(int data) {
        // TODO Auto-generated constructor stub
        this.data = data;
    }

    public int getData() {
        return data;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值