链表
1. 单链表
-
链表的介绍:链式存储,是有序的,但在内存中确实无序的。内存中存储如图所示:
-
链表是用节点来存储。其中结中有,data域和next域,data域为保存数据,next域中为指向下一个节点。
-
链表又分为带头节点和不带节点区别,区别不大,不同环境自己区分。带头节点链表示意图:
-
具体结构和单链表增删查改代码结构看,如下就是代码实现:
创建节点并创建单链表和实现类:
/**
* 创建单链表节点类
*/
class Node{
private int data;//数据域
private Node next;//next域指向下一个节点
public Node(int data) {
this.data = data;
}
@Override
public String toString() {
return "Node{" +
"data=" + data +
'}';
}
}
创建一个单链表:
/**
* 创建单链表,用于增删查改各种操作
*/
static class SingleLinkList{
//1.先初始化头节点
private Node head =new Node(0);
//2.编写获取头节点方法
public Node getHead() {
return head;
}
/**
* 3.功能:用于添加节点
* 添加思路:找到最后一个节点,将最后一个节点的next域指向新添加的节点
* @param node 新添加的节点
*/
public void add(Node node) {
//因为头节点不能动,所以借助临时变量去遍历找到最后一个节点
Node temp=head;
while (true){
if (temp.next==null){
break;
}
temp=temp.next;
}
//添加节点,将最后一个节点指向新节点
temp.next=node;
}
/**
* 4.功能:按值排序插入节点到应该到的位置。思路一致,
* 找到要插入位置的前一个节点,将该节点next指向找到前一个节点的next,前一个节点的next指向node
* @param node
*/
public void addByOrder(Node node) {
//因为头节点不能动,所以借助临时变量去遍历找到最后一个节点
Node temp=head;
while(true){
if(temp.next == null) {//说明temp已经在链表的最后
break; //
}
//如果满足该条件表示已经满足了,可以直接插入到temp后面了
if (temp.next.data>node.data){
break;
}else {
temp=temp.next;
}
}
//找到这个位置的前一个位置,表示与后面相连
node.next=temp.next;
//将node连接到链表中,表示已经插入成功。
temp.next=node;
}
/**
* 5.功能:找到该数据匹配的节点并删除,因为用于理解就没考虑没找到,和节点在在最后的情况
* @param data 要删除节点的数据
*/
public void deleteNode(int data){
//因为头节点不能动,所以借助临时变量去遍历找到最后一个节点
Node temp=head;
while(true){
if(temp.next == null) {//说明temp已经在链表的最后
break; //
}
//如果满足该条件的前一个节点
if (temp.next.data==data){
break;
}else {
temp=temp.next;
}
}
//删除节点,将要删除节点,指向temp的next的next
temp.next=temp.next.next;
}
/**
* 查询,这里就不单独查一节点了没有任何意义 就来整个链表
*/
public void list(){
Node temp=head;
while(temp.next!=null){
//将temp后移, 一定小心
temp=temp.next;
//输出节点的信息
System.out.println(temp);
}
}
/**
* 等于该值修改为新节点
* @param date 数据
* @param node 新节点
*/
public void updateNode(int date,Node node){
Node temp=head;
while (true){
if(temp.next.data==date){
break;
}
temp=temp.next;
}
node.next=temp.next.next;
temp.next=node;
}
}
两题小题了解一下单链表的应用:
1.查找单链表中的倒数第k个结点 【新浪面试题】代码实现如下:
思路就是遍历一次,算出为正多少,再去从length-k就是正的
//方法:获取到单链表的节点的个数(如果是带头结点的链表,需求不统计头节点)
/**
*
* @param head 链表的头节点
* @return 返回的就是有效节点的个数
*/
public static int getLength(Node head) {
if(head.next == null) { //空链表
return 0;
}
int length = 0;
//定义一个辅助的变量, 这里我们没有统计头节点
Node cur = head.next;
while(cur != null) {
length++;
cur = cur.next; //遍历
}
return length;
}
public static Node findLastIndexNode(Node head, int index){
//第二次遍历 size-index 位置,就是我们倒数的第K个节点
int size = getLength(head);
Node cur=head ;//因为目前是第零个
for(int i=0;i<=size-index;i++ ){
cur=cur.next;
}
return cur;
}
2.单链表反转
1.第一种思路如果单单输出,可以借助栈先进后出原则,遍历一遍将节点塞入栈中,输出即可,没有难度,可以借助容器。
2.第二种思路:使用两个单链表,遍历一个节点,将他放入新链表的最前端,重复操作,在头节点指向另一个链表的头next域这样就可以完成了,jvm看没有指向就会回收老链表,推荐使用。
代码如下:
//将单链表反转
public static void reversetList(Node head){
//定义一个辅助的指针(变量),帮助我们遍历原来的链表
Node cur = head.next;
Node next = null;// 指向当前节点[cur]的下一个节点
Node reverseHead = new Node(0);
while(cur != null) {
next = cur.next;//先暂时保存当前节点的下一个节点,因为后面需要使用
cur.next = reverseHead.next;//将cur的下一个节点指向新的链表的最前端
reverseHead.next = cur; //将cur 连接到新的链表上
cur = next;//让cur后移
}
head.next = reverseHead.next;
}
2. 双向链表
- 双向链表在单链表的基础上,增加了一个前驱pre,这样就方便了许多就不会之前单链表那种反转繁琐的问题,可以直接使用前驱和后继顺序和逆序遍历。
- 双向链表的节点结构就是,data域和前驱节点和后继节点
- 主要实现原理和结构重点看代码实现:
创建双向链表节点结构如下:
/**
* 创建双链表节点
*/
class Node{
public int data;
public Node pre;
public Node next;
public Node(int data) {
this.data = data;
}
@Override
public String toString() {
return "Node{" +
"data=" + data +
'}';
}
}
创建双向链表(对双向链表进行增删查改):
/**
* 创建双链表,并对双向链表进行增删查改
*/
static class DoubleLinkedList {
// 先初始化一个头节点, 头节点不要动, 不存放具体的数据
private Node head = new Node(0);
// 返回头节点
public Node getHead() {
return head;
}
// 添加一个节点到双向链表的最后.
public void add(Node node) {
Node temp = head;
while (true) {
// 找到链表的最后
if (temp.next == null) {
break;
}
}
temp.next = node;
node.pre = temp;
}
//按条件大小插入双向链表节点
public void addByOrder(Node node) {
Node temp = head;
while (true) {
// 找到链表的最后直接添加
if (temp.next == null) {
temp.next=node;
node.pre=temp;
break;
}
//找到要插入的这个节点,在该节点后面插入新节点
if (node.data > temp.data && node.data < temp.next.data) {
//将新增节点的next域指向插入后面值后一个节点
node.next = temp.next;
//将找到的后一个节点的前驱指向新增节点
temp.next.pre = node;
//将找到的结点指向新增节点
temp.next = node;
//将新增节点的前驱节点指向要插入之前这个节点
node.pre = temp;
break;
} else {
temp = temp.next;
}
}
}
/**
* 功能:删除节点
*
* @param data 删除节点值为data的节点
*/
public void deleteNode(int data) {
Node temp = head;
while (true) {
// 找到链表的最后
if (temp.next == null) {
break;
}
//找到要删除的节点
if (temp.data == data) {
//将temp的前驱节点指向temp的next节点
temp.pre.next = temp.next;
//将temp的next节点前驱指向temp的前驱
temp.next.pre = temp.pre;
break;
} else {
temp = temp.next;
}
}
}
/**
* 遍历该接节点
*/
public void printList() {
Node temp = head;
while (temp.next != null) {
temp = temp.next;
System.out.println(temp);
}
}
/**
* 将值为data的节点修改为新节点
*
* @param data 对应的值
* @param node 要新节点
*/
public void updateNode(int data, Node node) {
Node temp = head;
while (true) {
// 找到链表的最后
if (temp.next == null) {
break;
}
//找到要修改的节点
if (temp.data == data) {
//将新节点的后继指向老节点的后继
node.next = temp.next;
//将老节点的next节点前驱节点指向新节点
temp.next.pre = node;
//将老节点的前驱的next域指向新节点
temp.pre.next = node;
//将新节点的前驱指向老节点的前驱
node.pre = temp.pre;
break;
}
temp=temp.next;
}
}
}
3. 单向循环链表
思路:创建一个循环链表,使用最后一个指向头节点,组成循环链表,报数到几就出栈,剩下一个指向自己,直到循环列表为空就说明全部出列了。
代码如下:
1.创建人对象节点:
class Boy{
int no;
Boy next;
public Boy(int no) {
this.no = no;
}
@Override
public String toString() {
return "Boy{" +
"no=" + no +
'}';
}
}
2.**创建循环列表,**并写该算法,出队列,思路就思路,用一个节点first表示要出队列的节点,一个辅助指针指向尾,一直循环这样的情况就可以出队,可以细看代码就清晰了。
/**
* 循环列表
*/
class JosePhuLinkList{
// 创建一个first节点,当前没有编号
private Boy first = null;
/**
* 功能:创建小孩
* @param num
*/
public void addBoy(int num){
Boy curBoy = null; // 辅助指针,帮助构建环形链表
for(int i=1;i<=num;i++){
Boy boy = new Boy(i);
//如果是第一个节点,将第一个节点插入,并next指向自己,组成循环链表
if(i==1){
first=boy;//第一个节点
first.next=first;//自己指向自己用于构建循环链表
curBoy=first;//让curBoy指向第一个小孩
}else {
curBoy.next=boy;//连接新节点
boy.next=first;//指向头节点,继续循环链表
curBoy=boy;//后移一直指向尾结点
}
}
}
public void showBoys(){
// 因为first不能动,因此我们仍然使用一个辅助指针完成遍历
Boy curBoy = first;
while(true){
System.out.println(curBoy);
//因为循环链表,单最后一个结点的next为头时候就表明结束,没人了
if(curBoy.next==first){
break;
}
curBoy=curBoy.next;//对他进行后移
}
}
/**
*功能:根据用户输入的起始节点位置,数几次,进行出列
* @param startNo 开始数数的节点
* @param countNum 表示数几次例如(数2,就数到2的出列)
* @param nums 表示圈中有几个人
*/
public void countBoy(int startNo, int countNum, int nums) {
// 创建要给辅助指针,帮助完成小孩出圈
Boy helper = first;//为指向最后一个节点
while (true) {
if (helper.next == first) { // 说明helper指向最后小孩节点
break;
}
helper = helper.next;
}
//小孩报数前,先让 first 和 helper 移动 k - 1次,缺点头尾,头往后移,尾也是往后移
for(int j = 0; j < startNo - 1; j++) {
first = first.next;
helper = helper.next;
}
//当小孩报数时,让first 和 helper 指针同时 的移动 m - 1 次, 然后出圈
//这里是一个循环操作,知道圈中只有一个节点
while (true){
if(helper == first) { //说明圈中只有一个节点
break;
}
//往后移
for(int i=0;i<countNum-1;i++){
first = first.next;
helper = helper.next;
}
//这时first指向的节点,就是要出圈的小孩节点
System.out.println(first.no);
//这时将first指向的小孩节点出圈
first = first.next;
helper.next=first;
}
System.out.println(first.no);//最后一个出圈的就是对尾就是头只有一个节点就出
}
}