数据结构还是很重要的,就算不是那种很牛逼的,但起码得知道基础的东西,这一系列就算是复习一下以前学过的数据结构和填补自己在这一块的知识的空缺。加油。珍惜校园中自由学习的时光。按照链表、栈、队列、排序、数组、树这种顺序来学习数据结构这门课程把。
-WH
一、单链表的概念
链表是最基本的数据结构,其存储的你原理图如下图所示
上面展示的是一个单链表的存储原理图,简单易懂,head为头节点,他不存放任何的数据,只是充当一个指向链表中真正存放数据的第一个节点的作用,而每个节点中都有一个next引用,指向下一个节点,就这样一节一节往下面记录,直到最后一个节点,其中的next指向null。
链表有很多种,比如单链表,双链表等等。我们就对单链表进行学习,其他的懂了原理其实是一样的。
二、用java实现单链表
语言只是一种工具,数据结构真正体会的是那种思想,这句话确实是这样,不管用什么写,其思想是不改变的。以前使用的是C++,现在用的是java,一步步来实现。
2.1、编写一个Node类来充当结点的模型。我们知道,其中有两个属性,1存放数据的data,2存放下一结点的引用,
package com.wuhao.demo01; public class Node { //为了方便,这两个变量都使用public,而不用private就不需要编写get、set方法了。 //存放数据的变量,简单点,直接为int型 public int data; //存放结点的变量,默认为null public Node next; //构造方法,在构造时就能够给data赋值 public Node(int data){ this.data = data; } }
2.2、单链表的简单操作(增加,删除,获取总长度,链表元素排序,链表遍历)
2.2.1、增加结点操作,addNode(Node)
想法: 一开始也会想如果什么结点也没有。是不是需要判断插入的是第一个结点的问题,但写完后发现没有必要,是不是第一个结点操作都是一样的,所以通过移动的指针遍历整个链表,找到最后一个结点,往后添加即可。没难度。
/** * 增加操作 * 直接在链表的最后插入新增的结点即可 * 将原本最后一个结点的next指向新结点 */ public void addNode(Node node){ //链表中有结点,遍历到最后一个结点 Node temp = head; //一个移动的指针(把头结点看做一个指向结点的指针) while(temp.next != null){ //遍历单链表,直到遍历到最后一个则跳出循环。 temp = temp.next; //往后移一个结点,指向下一个结点。 } temp.next = node; //temp为最后一个结点或者是头结点,将其next指向新结点 }
2.2.2、插入结点到链表的指定位置。 insertNodeByIndex(int index,Node node)
注意:要知道插入操作需要的前提是什么,你才好写代码,写完之后,考虑如果在特殊位置上插入,是否也一样。还有需要进行插入位置是否可行的判断。
/** * insertNodeByIndex:在链表的指定位置插入结点。 * 插入操作需要知道1个结点即可,当前位置的前一个结点 * index:插入链表的位置,从1开始 * node:插入的结点 */ public void insertNodeByIndex(int index,Node node){ //首先需要判断指定位置是否合法, if(index<1||index>length()+1){ System.out.println("插入位置不合法。"); return; } int length = 1; //记录我们遍历到第几个结点了,也就是记录位置。 Node temp = head; //可移动的指针 while(head.next != null){//遍历单链表 if(index == length++){ //判断是否到达指定位置。 //注意,我们的temp代表的是当前位置的前一个结点。 //前一个结点 当前位置 后一个结点 //temp temp.next temp.next.next //插入操作。 node.next = temp.next; temp.next = node; return; } temp = temp.next; } }
2.2.3、删除指定位置上的结点 delNodeByIndex(int index)
/** * 通过index删除指定位置的结点,跟指定位置增加结点是一样的,先找到准确位置。然后进行删除操作。 * 删除操作需要知道1个结点即可:和当前位置的前一个结点。 * @param index:链表中的位置,从1开始 * */ public void delNodeByIndex(int index){ //判断index是否合理 if(index<1 || index>length()){ System.out.println("给定的位置不合理"); return; } //步骤跟insertNodeByIndex是一样的,只是操作不一样。 int length=1; Node temp = head; while(temp.next != null){ if(index == length++){ //删除操作。 temp.next = temp.next.next; return; } temp = temp.next; } }
2.2.4、单链表进行选择排序 selectSortNode()
前提要知道什么是选择排序,如果不会,请查看我讲解排序的文章
分析
/** * 对链表中的结点进行排序,按照从小到大的顺序,使用选择排序。 * 使用双层遍历。第一层遍历,正常遍历链表,第二层遍历,遍历第一层遍历时所用的结点后面所有结点并与之比较 * 选择排序比较简单,明白其原理,就能够写的出来。 */ public void selectSortNode(){ //判断链表长度大于2,不然只有一个元素,就不用排序了。 if(length()<2){ System.out.println("无需排序"); return; } //选择排序 Node temp = head; //第一层遍历使用的移动指针,最处指向头结点,第一个结点用temp.next表示 while(temp.next != null){ //第一层遍历链表,从第一个结点开始遍历 Node secondTemp = temp.next; //第二层遍历使用的移动指针,secondTemp指向第一个结点,我们需要用到是第二个结点开始,所以用secondNode.next while(secondTemp.next != null){//第二层遍历,从第二个结点开始遍历 if( temp.next.data > secondTemp.next.data){ //第二层中的所有结点依次与第一次遍历中选定的结点进行比较, int t = secondTemp.next.data; secondTemp.next.data = temp.next.data; temp.next.data = t; } secondTemp = secondTemp.next; } temp = temp.next; } }
2.2.5、单链表进行插入排序 insertSortNode()
前提:要知道什么是插入排序。这个使用插入排序写了我好久,懵逼一样的状态,并且我认为我自己写效率不是很高。不管怎样,是骡子是马拿出来溜溜
/** * 对链表进行插入排序,按从大到小的顺序,只要这里会写,那么手写用数组插入排序 * 也是一样的。先要明白原理。什么是插入排序,这样才好写代码。 * 插入排序:分两组,一组当成有序序列,一组当成无序,将无序组中的元素与有序组中的元素进行比较(如何比较,那么就要知道插入排序的原理是什么这里不过多阐述) * 这里我想到的方法是,构建一个空的链表当成有序序列,而原先的旧链表为无序序列,按照原理,一步步进行编码即可。 * */ public void insertSortNode(){ //判断链表长度大于2,不然只有一个元素,就不用排序了。 if(length()<2){ System.out.println("无需排序"); return; } //创建新链表 Node newHead = new Node(0); //新链表的头结点 Node newTemp = newHead; //新链表的移动指针 Node temp = head; //旧链表的移动指针 if(newTemp.next == null){ //将第一个结点直接放入新链表中。 Node node = new Node(temp.next.data); newTemp.next = node; temp = temp.next; //旧链表中指针移到下一位(第二个结点处)。 } while(temp.next != null){ // 遍历现有链表 while(newTemp.next != null){ //先跟新链表中的第一个结点进行比较,如果符合条件则添加到新链表,注意是在第一个位置上增加结点 //如果不符合,则跟新链表中第二个结点进行比较,如果都不符合,跳出while,判断是否是到了新链表的最后一个结点,如果是则直接在新链表后面添加即可 if(newTemp.next.data < temp.next.data){ Node node = new Node(temp.next.data); node.next = newTemp.next; newTemp.next = node; break; } newTemp = newTemp.next; } if(newTemp.next == null){//到达最末尾还没符合,那么说明该值是新链表中最小的数,直接添加即可到链表中即可 //直接在新链表后面添加 Node node = new Node(temp.next.data); newTemp.next = node; } //旧链表指针指向下一位结点,继续重复和新链表中的结点进行比较。 temp = temp.next; //新链表中的移动指针需要复位,指向头结点 newTemp = newHead; } //开始使用新链表,旧链表等待垃圾回收机制将其收回。 head = newHead; }
2.2.6、当然还可以使用冒泡排序、归并排序,等等等,都可以自己去尝试,我就不写了。如果不懂这些排序,那么就看我写排序的文章把。
2.2.7、计算单链表的长度
/** * 计算单链表的长度,也就是有多少个结点 * @return 结点个数 */ public int length() { int length=0; Node temp = head; while(temp.next != null){ length++; temp = temp.next; } return length; }
2.2.8、遍历单链表,打印data
/** * 遍历单链表,打印所有data */ public void print(){ Node temp = head.next; while(temp != null){ System.out.print(temp.data+","); temp = temp.next; } System.out.println(); }
三、总结
基本的单链表操作就是上面这些了,自己完全手动写一写,会对单链表这种数据结构的理解有很大的帮助。当然其中比较难的是在于对链表的排序上,说难也不难,只要懂的几种排序的原理,就跟写伪代码一样,去实现即可。这里还有一些操作,大家可以动手做一做,我会在下一篇文章中讲解这些答案。
3.1、如何从链表中删除重复数据
3.2、如何找出单链表中的倒数第k个元素
3.3、如何实现链表的反转
3.4、如何从尾到头输出单链表
3.5、如何寻找单链表的中间结点
3.6、如何检测一个链表是否有环
3.7、如何在不知道头结点的情况下删除指定结点
3.8、如何判断两个链表是否相交