数据结构与算法(JAVA)
持续修改更新…
顺序表与链表
顺序表
物理地址连续
顺序表的长度是存储在顺序表中的有效数字的个数
数组扩容:当插入元素数组空间满时,应进行扩容
Arrays.copyOf(this.elem,2*this.elem.length)
例:顺序表基础代码:
import java.util.Arrays;
//顺序表
public class MyArrayList {
public int[] elem;
public int usedesize;
public static final int capacity=4;//初始容量
public MyArrayList(){
this.elem=new int[capacity];//数组一定要初始化,不然默认为空
this.usedesize=0;//有效数据个数
}
private boolean isFull(){
if(this.usedesize==this.elem.length){
return true;
}
else return false;
}
public static void main(String[] args) {
MyArrayList myArrayList=new MyArrayList();
myArrayList.add(0,2);
myArrayList.add(1,2);
myArrayList.add(2,12);
myArrayList.add(3,12);
myArrayList.display();
System.out.println(myArrayList.contains(12));
System.out.println(myArrayList.search(12));
System.out.println(myArrayList.getPos(2));
System.out.println(myArrayList.getSize());
myArrayList.remove(12);
myArrayList.display();
}
//打印顺序表
public void display(){
for (int i = 0; i < this.usedesize; i++) {
System.out.print(this.elem[i]+"\t");
}
System.out.println();
}
//在pos位置新加元素
/*
pos是下标
判断pos位置是否合法,负数,>usedSize
pos不能是数组最后一个元素,除非前面都插过数据元素
挪数据,i=usedsize-1,i下标值赋给i+1下标值
this.elem[pos]=data
*/
public void add(int pos,int data){
//顺序表满的情况
if(this.isFull()){
//数组扩容
Arrays.copyOf(this.elem,2*this.elem.length);
}
if(pos<0||pos>this.usedesize){
return;
}
else {
this.usedesize=this.usedesize+1;
System.out.println(this.usedesize);
for (int i = this.usedesize - 2; i >=pos; i--) {
this.elem[i + 1] = this.elem[i];
}
this.elem[pos]=data;
}
}
//判定是否包含某个元素 12
public boolean contains(int toFind){
for (int i = 0; i < this.usedesize; i++) {
if(this.elem[i]==toFind){
return true;
}
}
return false;
}
//查找某个元素对应位置
public int search(int toFind){
for (int i = 0; i < this.usedesize; i++) {
if(this.elem[i]==toFind){
return i;
}
}
return -1;
}
//获取pos位置的关键字
public int getPos(int pos){
//顺序表是为空
if(this.usedesize==0){
throw new RuntimeException("pos位置不合法");
}
if(pos<0||pos>this.usedesize){
return -1;
}
//pos的合法性
return this.elem[pos];
}
public int getSize(){
return this.usedesize;
}
//删除第一次出现的关键字
public void remove(int toRemove){
int pos=search(toRemove);
if(pos==-1){
System.out.println("没有要删除的数字");
return;
}
for(int i=pos;i<this.usedesize-1;i++){
this.elem[i]=this.elem[i+1];
}
this.usedesize--;
}
}
单链表
单向非循环链表
单链表插入要先绑住后面的节点
- 易错点:
有的循环使拍p!=null,则证明最后一个结点遍历过了,如果是P.next!=null,则证明不遍历最后一个结点。
JVM在回收内存是,当该对象没有人引用它的时候,这个对象才会被回收。释放内存clear()
例:单链表基础代码
class node{
public int data;
public node next;
public node(int data){
this.data=data;
this.next=null;
}
}
public class SingleList {
public node head;//保存单链表的头结点的引用
public static void main(String[] args) {
SingleList s=new SingleList();
s.addFirst(2);
s.addLast(10);
s.addLast(14);
s.addLast(13);
s.addLast(14);
s.addLast(15);
s.display();
System.out.println(s.contains(15));
System.out.println(s.size());
s.addindex(3,12);
s.display();
s.remove(13);
s.display();
s.removeall(14);
s.display();
}
//显示单链表内容
public void display(){
node p=this.head;
while(p!=null){
System.out.print(p.data+" ");
p=p.next;
}
System.out.println();
}
//计算单链表长度
public int size(){
node p=this.head;
int length=0;
while(p!=null){
p=p.next;
length++;
}
return length;
}
//头插法
public void addFirst(int data){
//新建节点
node node1=new node(data);
if(this.head==null){
this.head=node1;
return;
}
//无头结点的插入方法
node1.next=this.head;
this.head=node1;
}
//尾插法
public void addLast(int data){
node node1=new node(data);
if(this.head==null){
this.head=node1;
return;
}
node t=this.head;
while(t.next!=null){
t=t.next;
}
t.next=node1;
}
//查找是否包含key
public boolean contains(int key){
node p=this.head;
while(p!=null){
if(p.data==key){
return true;
}
p=p.next;
}
return false;
}
//任意位置插入,第一个数据节点为0节点
public void addindex(int index,int data){
//首先找到插入的位置
if(index==0){
this.addFirst(data);
}
if(index==this.size()){
this.addLast(data);
}
node node1=new node(data);
node p=this.searchindex(index);
node1.next=p.next;
p.next=node1;
}
//查到插入节点前一个位置的节点
public node searchindex(int index){
if(index<0||index>this.size()){
throw new RuntimeException("index位置不合法");
}
int i=0;
node node1=this.head;
//插到第index位置,走index-1步
while(i!=index-1){
i++;
node1=node1.next;
}
return node1;
}
//找第一次出现关键字为key的节点的前驱
public node search(int key){
node p=this.head;
while(p.next!=null){
if(p.next.data==key){
return p;
}
p=p.next;
}
return null;
}
//删除关键字为key的节点
public void remove(int key){
//考虑头结点为空
if(this.head==null){
return;
}
//考虑删除节点是头结点
if(this.head.data==key){
this.head=this.head.next;
return;
}
node p=search(key);
node q=p.next;
p.next=q.next;
}
//删除所有值为key的节点,单链表只需遍历一遍
/*
问题:两个指向,首先pre指向值为key的结点的前驱,cur指向当前结点,
当cur.data=key时,删除当前节点,pre.next=cur.next;cur=cur.next
当cur.data!=key时,两个指针都移动,pre=cur;cur=cur.next
注意:先判断后面再判断头结点是否等于key,
因为cur永远是从pre的后继结点开始的,所以极有可能会跳过头结点
*/
public void removeall(int key){
node pre=this.head;
node cur= pre.next;
while(cur!=null){
if(cur.data==key){
pre.next=cur.next;
}
else{
pre=cur;
}
cur=cur.next;
}
if(this.head.data==key){
this.head=this.head.next;
}
}
//释放内存,清除所有节点
public void clear(){
this.head=null;
}
}
单链表面试题
1.只需遍历单链表一遍实现倒置(**)
方法1:
/*
cur代表当前需要翻转的节点,curnext代表下一个需要翻转的节点,
p初始为空,作为逆转后的最后一个空结点
*/
public void reverse(){
node cur=this.head;
node curnext;
node p=null;
while (cur!=null) {
curnext = cur.next;
//当cur为单链表最后一个时,cur.next=null,将其作为新的头结点
if (cur.next == null) {
this.head = cur;
}
//结点移动,
cur.next = p;
p = cur;
cur = curnext;
}
}
方法2:利用头插法
//利用头插法实现倒置
public void reverse2(){
node curnext=this.head.next;//从第一个元素开始
this.head.next=null;//先将头结点的next域设为null
while(curnext!=null){
node p= curnext.next;//依次将元素节点取下,暂存curnext的后继
curnext.next=this.head;//将curnext插在头结点之后
this.head=curnext;
curnext=p;
}
}
2.给定一个带有头结点head的非空单链表,返回链表的中间结点,如果有两个中间结点则返回第二个中间结点,(仅遍历单链表一遍)
//双指针fast slow;fast一次走两步,slow一次走1步,当fast走完时,low所在位置为中间结点
//fast的速度是slow的2倍,fast走完时,slow必在中间
public int middle(){
node fast=this.head;
node low=this.head;
//如果fast!=null且fast.next!=null,fast肯定还没走完,如果走到尾巴,slow必在中间
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
low=low.next;
}
return low.data;
}
3.输入一个链表,输出该链表中倒数第K个结点。(遍历单链表一遍)
//找倒数第k个结点
public int searchk(int k){
node fast=this.head;
node low=this.head;
//k的合法性
if(k<0||k>size()){
new RuntimeException("k只越界");
}
k=k-1;
/*让快的先走k-1步,当fast.next==null时,fast刚好是最后一个
此处疑问:与遍历整个列表的循环条件不同,
遍历整个链表的循环条件是cur!=null,当循环到最后一个结点时,curc=cur.next=null,链表已经遍历完
而本题要达到的是fast走到最后一个节点结束,所以当fast到达最后一个节点时就停止循环。*/
while(fast.next!=null){
for (int i = 0; i < k; i++) {
fast=fast.next;
}
low=low.next;
fast=fast.next;
System.out.println(fast.data);
}
return low.data;
}
4.以给定值x为基准将链表分为两部分,所有小于x的节点排在大于等于x的节点的节点之前。
思路:尾插法,将原链表分成两部分,
bs一部分放置比x小的节点,as一部分放置比X大的节点,为保持序列不变,使用尾插法。
注意:第一个段没有数据,返回as
as与bs进行拼接,bs.next=as
//将所有小于x的节点排在大于等于x之前,分割后保持原本序列不变
public void sqeueue(int x) {
node cur = this.head;
node as = null;
node bs = null;
node ae = null;
node be = null;
while (cur != null) {
if (cur.data < x) {
//第一次插入
if (bs == null) {
bs = cur;
be = cur;
} else {
be.next = cur;
be = be.next;
}
} else {
if (as == null) {
as = cur;
ae = cur;
} else {
ae.next = cur;
ae = ae.next;
}
}
cur = cur.next;
}
//如果ae不为空,ae的next需要置为空
if (ae != null) {
ae.next = null;
}
//判断bs是否为空,如果bs=null,返回as
if (bs == null) {
this.head=as;
}else{
//如果bs不为空,需要进行拼接
be.next = as;
this.head = bs;}
}
5.在一个排序的链表中,存在重复的节点,请删除该链表中重复的节点,重复的节点不保留,返回链表头指针。
//在一个排序的链表中,存在重复的节点,
// 请删除该链表中重复的节点,重复的节点不保留,返回链表头指针
public void deleteDuplication(){
/*题中是排序的链表,所以重复的都在一起
1.定义一个虚拟节点来解决问题newhead;
2.判断链表中,当前相邻节点是否相等,如果相等,
*/
node newhead=new node(-1);//虚拟节点
node tmp=null;
node cur=this.head;
while(cur!=null){
if(cur.next!=null&&cur.data==cur.next.data){
//将cur.data放入新的双链表中,实现尾插法
while(cur.next!=null&&cur.data==cur.next.data){
cur=cur.next;
//退出循环让cur再多走一步
cur=cur.next;
}
}else{
tmp.next=cur;
tmp=tmp.next;
cur=cur.next;
}
}
tmp.next=null;
this.head=newhead;
}
6.链表的回文结构(12321,123321):给定一个链表的头指针,请返回一个bool值,代表其是否为回文结构。
public boolean huiwen() {
/*
找到中间结点,1-》2-》3《-2《-1
开始翻转单链表
一个从头走一个从尾走的进行翻转
*/
//单链表为空
if (this.head == null) {
return false;
}
//只有头结点
if (this.head.next == null) {
return true;
}
node slow = this.head;
node fast = this.head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
//翻转单链表后半部分,slow肯定在中间位置
node cur = slow.next;
while (cur != null) {
node curnext = cur.next;
cur.next = slow;
slow = cur;
cur = curnext;
}
//slow已经是最后一个节点
//一个从头走一个从尾走
while (slow != this.head) {
if (slow.data != this.head.data) {
return false;
} //判断偶数的情况
if (this.head.next == slow) {
return true;
}
slow = slow.next;
this.head = this.head.next;
}
return true;
}
7.将两个有序链表合并成新的升序链表
public static node combinetwo(node headA, node headB) {
node newhead = new node(-1);
node tmp = newhead;
while (headA != null && headB!= null) {
if (headA.data <headB.data) {
tmp.next= headA;
headA= headA.next;
tmp=tmp.next;
}
else {
tmp.next= headB;
headB = headB.next;
tmp=tmp.next;
}
}
if(headA!=null){
tmp.next=headA;
}
if(headB!=null){
tmp.next=headB;
}
return newhead.next;
}
//主函数测试代码
node node1=combinetwo(s.head,s1.head);
while(node1.next!=null){
System.out.println(node1.data+" ");
node1=node1.next;
}
8.给定一个链表判断是否有环
//给定一个链表判断链表中是否有环
/*d定义两个指针,设置不同速度,看二者最后是否能够相遇,相遇则证明有环
fast一次走两步
slow一次走一步
问:一个走3步,一个走1步可以吗?
要么很慢才能相遇,要么永远相遇不了(走三步的肯定有擦肩而过的情况,中间相邻,导致相遇比较复杂,时间较长)
fast=fast.next.next
slow=slow.next
fast==next||fast.next==null一定没有环
有环的话肯定不为空
每走一步判断slow==fast,说明有环
*/
public boolean hascycle(){
node fast=this.head;
node slow=this.head;
while(slow!=fast){
if(fast==null||fast.next==null){
return false;
}
fast=fast.next.next;
slow=slow.next;
}
return true;
}
9.给定一个链表返回链表开始入环的第一个节点,如果链表没环,则返回为null(**)
从head到入口点长度为X从相遇点到入口点长度也为X,所以下面让slow从头走,让fast从相遇点走,二者速度都为1,则下次相遇点就是环的 入口点
//如果有环,fast和slow相遇。fast走的路程是Slow的二倍
//给定一个链表返回链表开始入环的第一个节点,如果链表没环,则返回为null
public node detectcycle(){
node fast=this.head;
node slow=this.head;
while(fast!=null||fast.next!=null){
if(slow==fast){
break;
}
fast=fast.next.next;
slow=slow.next;
if(fast==null||fast.next==null){
return null;
}
}
slow=this.head;
while(fast!=slow){
fast=fast.next.next;
slow=slow.next;
}
return fast;
}
10.编写一个程序找到两个链表的相交节点
//编写一个程序找到两个链表的相交节点
//创建相交链表、测试代码
public static void createcut(node headA,node headB){
headA.next=headB.next.next;
}
public static node getIntersectionNode(SingleList headA,SingleList headB){
node pl=null;
node ps=null;
int len;
int lenA=headA.size();
int lenB=headB.size();
if((lenA-lenB)>0){
pl=headA.head;
ps=headB.head;
len=lenA-lenB;
}
else{
pl=headB.head;
ps=headA.head;
len=lenB=lenA;
}
for (int i = 0; i < len; i++) {
pl=pl.next;
}
while(ps!=pl){
ps=ps.next;
pl=pl.next;
}
//ps等于pl二者相遇,并且PL!=null,ps!=null
if(pl!=null&&ps!=null){
return pl;}
return null;
}
//测试代码
//两个链表头部扔进去
createcut(s.head,s1.head);
System.out.println(getIntersectionNode(s1, s).data);
双链表
例:双链表基本操作
class nodedouble{
public int data;
public nodedouble pre;
public nodedouble next;
public nodedouble(int data) {
this.data = data;
}
}
public class LinkedList{
public nodedouble head;//双向链表的头
//此处也可加入一个双向链表的尾巴结点,方便尾插入
public static void main(String[] args) {
LinkedList list=new LinkedList();
list.addhead(2);
list.addhead(3);
list.addhead(4);
list.addtail(1);
list.addtail(1);
list.addtail(1);
list.display();
System.out.println(list.searchkey(3));
System.out.println(list.size());
list.insertindex(0,5);
list.deletefirst(3);
list.deleteall(1);
list.display();
list.Clear();
list.display();}
//头插法
public void addhead(int data) {
nodedouble node1 = new nodedouble(data);
if (this.head == null) {
this.head = node1;
this.head.next = null;
} else {
node1.next = this.head;
this.head.pre = node1;
this.head = node1;
}
}
public void display(){
nodedouble p=this.head;
while(p!=null){
System.out.print(p.data+" ");
p=p.next;
}
System.out.println();
}
//尾插法
public void addtail(int data){
nodedouble node1=new nodedouble(data);
nodedouble cur=null;
if(this.head==null){
/*
第一次后插元素时即当前双链表为空
*/
this.head=node1;
this.head.pre=null;
}
else{
//找到当前链表最后一个元素
cur=this.head;
while(cur.next!=null){
cur=cur.next;
}
cur.next=node1;
node1.pre=cur;
cur=node1;
}
}
//计算长度
public int size(){
nodedouble node1;
int size=0;
node1=this.head;
while(node1!=null){
size++;
node1=node1.next;
}
return size;
}
//查看是否存在key这个关键字
public boolean searchkey(int key){
nodedouble cur=this.head;
while(cur.next!=null){
if(cur.data==key){
return true;
}
cur=cur.next;
}
return false;
}
//检查插入位置的合法性
public boolean checkindex(int index){
if(index<0||index>size()){
return false;
}
return true;
}
//在任意位置插入data
public void insertindex(int index,int data){
nodedouble node1=new nodedouble(data);
nodedouble cur;
if(this.head==null){
this.head=node1;
return;
}else{
if(index==0){
//当插入位置是头结点处时
this.head.pre=node1;
node1.next=this.head;
this.head=node1;
return;
}
cur=this.head;
//遍历双链表找到插入位置
for (int i = 0; i < index ;i++) {
cur=cur.next;
}
cur.pre.next=node1;
node1.next=cur;
node1.pre=cur.pre;
cur.pre=node1;
}
}
//删除第一次出现关键字为key的节点
public void deletefirst(int data){
nodedouble cur=this.head;
//如果删除结点是头结点
if(this.head.data==data){
this.head=this.head.next;
this.head.next.pre=null;
return;
}
while(cur.next!=null){
if(cur.data==data){
cur.pre.next=cur.next;
cur.next.pre=cur.pre;
return;
}
cur=cur.next;
}
//当删除结点是尾巴结点时
if(data==cur.data){
cur.pre.next=null;
}
}
//删除所有Key
public void deleteall(int data) {
nodedouble cur = this.head;
//如果删除结点是头结点
if (this.head.data == data) {
this.head = this.head.next;
this.head.next.pre = null;
return;
}
while (cur.next != null) {
if (cur.data == data) {
cur.pre.next = cur.next;
cur.next.pre = cur.pre;
//此处不用return结束,让cur继续往后走
}
cur = cur.next;
}
//当删除结点是尾巴结点时
if (data == cur.data) {
cur.pre.next = null;
}
}
//一个一个节点进行释放
public void Clear(){
while (this.head != null) {
nodedouble node=head.next;
head.next = null;
head.pre = null;
head=node;
}
}
}