前幾天,回了老家,怠惰了幾天,把雲圖看了,還看了一季的King of the hill ,簡直爽爆,回來了,又要開始學習了,今天我們來學習幾種極爲常見的數據結構,分別是揹包、隊列還有棧。
1.3.1:
知識點:
泛型:在java中,我們可以規定集合類型的一個關鍵屬性,像這樣(Bag<pig> bag=new Bag<pig>),那麼這個包就只能往裏放pig類型的對象,當你往裏面放入其他類型時,就會編譯報錯,能自動裝箱轉換的類型除外。
自動裝箱:由於泛型只能設置爲引用類型的數據,當我們想限制集合只能是某原始數據類型,如int,float,double之類的,我們只能先將它們變成對應的引用類型,如Int,Float,Double,這樣就能成爲泛型,當我們設置了這些引用類型時,我們往集合存數據,就會自動裝箱,將該原始數據類型轉成他的引用類型,簡稱自動裝箱。
下面就進入數據結構的學習:
揹包:
揹包是一種不支持刪除元素的集合類型,主要用於收集元素,迭代遍歷所有元素。光進不出,想看可以,拿走?不可能。
隊列:
隊列遵循先進先出,先進隊列的,能先提取出來,就像排隊一樣,不多說了。
棧:
棧的話遵循後進先出,先進去的被壓在底下,後進去的在面上,就想做蛋糕,最後放的那層水果最先吃,蛋糕胚子最後。
講個例子:算術運算符壓棧之後怎麼處理?在p81其實講的非常清晰,整條運算式子從左往右掃描,左括號忽略,數值和運算符分別壓棧,當出現右括號時,數值棧出棧兩個,運算符棧出棧一個,運算結果入棧,直到掃描完成,得到最終結果。
1.3.2:
定容棧:即在實例化的時候,設定好了棧的大小,超出棧大小的時候,不會再往裏壓數據。
迭代器的api:hasnext,檢查棧內元素個數是否大於1,next將一個元素出棧。
動態調整數組的棧:由於棧用數組實現,數通組有固定大小,當棧大小達到上線時,可以更換更大的數組,然後將數據轉移,更換引用對象,從而動態調整棧的大小。
1.3.3:
鏈表:
鏈表並不是java直接支持的一種數據結構,C++上用鏈表方便多了,在java上鍊表,我們就需要自己寫鏈表的類,設置它的API,我們需要時根據不同的鏈表,設計不同的節點(當單向鏈表時,我們設計node的時候,我們只需要設計一個指向下一個node的成員變量,還有一個成員變量要指向一個自己的元素,雙向鏈表的話就還要設計一個指向上一個節點的成員變量)
鏈表的操作:①表頭插入節點,將表頭的next指向該節點,該節點的next指向原來的首節點。
②表頭刪除節點,將表頭指向首節點.next,結束。
③表尾插入節點,這個就比較難處理了,我們需要創建一個引用指向末節點,當我們需要在表尾添加節點時,用該引用找到末節點,設置末節點的next,而且修改對末節點的引用變量的引用,改爲引用新加入的節點。
④其他節點的插入,想插在那個節點後面則,先將要插入節點的next設置爲插入位置的next對應的節點,再講插入的位置的next設置爲要插入節點。
接下來就是做題部分了,這個章節的題目好像有點多哦,開工
1.3.1 我們看回p82,定容棧是用數組實現的,我們要想知道,棧是否滿,我們只需要比較一下size是否等於棧的定容就好了,不給代碼了,大概知道知道意思就好了。
1.3.2 沒什麼好說的
1.3.3 對於這個問題,我們能憑藉自己的大腦記憶來判斷棧出入是否正確,但是若果出入棧的數字變多了呢,100你能記憶嗎?
所以我們要從中找到其規律,以下是我參照網絡之後的思路,我們將入棧序列和出棧序列同時入棧,然後講一個引用指向各自的棧頂,當引用的數字相同時出棧,不一樣時,入棧的引用往後移,若最後兩個棧都空了,則序列合法,兩個棧不爲空則不合法,我還是我代碼整出來吧。
public boolean isLegal(int[] in, int[] out) {
if (in.length != out.length || in.length == 0) {
return false;
}
//輸入的棧
LinkedList<Integer> ins=new LinkedList<Integer>();
int j=0;
for (int i = 0; i < in.length; i++) {
//輸入壓棧
ins.push(in[i]);
//然後在輸出上找當前棧頂是否匹配當前輸出位置
while(ins.size()>0&&ins.peek()==out[j]){
ins.pop();
j++;
}
}
return ins.size()>0?false:true;
}
1.3.4 這個問題跟前面提到的算術式的壓棧其實非常類似的,我就直接上代碼吧
public boolean Parenthess(String s){
char[] chars = s.toCharArray();
LinkedList<Character> inS=new LinkedList<Character>();
//爲了防止空棧直接進一個右邊括號,peek的時候出錯
inS.push(' ');
for(int i=0;i<chars.length;i++){
//若是右邊部分,就進行配對,配上了就出棧,配不上就絕對是不合法的
if(chars[i]==')'){
if(inS.peek()=='('){
inS.pop();
continue;
}else {
return false;
}
}
if(chars[i]==']'){
if(inS.peek()=='['){
inS.pop();
continue;
}else {
return false;
}
}
if(chars[i]=='}'){
if(inS.peek()=='{'){
inS.pop();
continue;
}else {
return false;
}
}
//若不是右邊部分就正常壓棧
inS.push(chars[i]);
}
//最後看看有沒有沒被匹配出棧的左邊的括號,有就返回錯,無則返回true
if(inS.size()>1){
return false;
}else {
return true;
}
}
1.3.7 peek:stack上有一個first棧頭,返回first.item就好了。
1.3.9 補全左括號,跟那個運算式子有點關係,直接上代碼吧
public void becomeWhole(String s){
String[] strings = s.split("");
Stack<String> whole=new Stack<String>();
Stack<String> operaters=new Stack<String>();
//正則,用來匹配數字和運算符
String pattern="[0-9]";
String pattern1="(\\+|\\-|\\*|\\/)";
Pattern p=Pattern.compile(pattern);
Pattern p1=Pattern.compile(pattern1);
Matcher matcher;
Matcher matcher1;
for(int i=0;i<strings.length;i++){
//如果是0-9的話壓進全棧
//如果是+-*/就壓進運算符棧
matcher=p.matcher(strings[i]);
matcher1=p1.matcher(strings[i]);
if(matcher1.find()){
operaters.push(strings[i]);
continue;
}
if(matcher.find()){
whole.push(strings[i]);
continue;
}
//如果是),whole彈兩個,運算符彈一個,加上括號組成真正的運算式
if(strings[i].equals(")")){
String s1=whole.pop();
String s2=whole.pop();
String o1=operaters.pop();
whole.push("("+s2+o1+s1+")");
}
}
System.out.println(whole.pop());
}
1.3.10 中序換成後序,我們首先思考一個問題,什麼是前序,中序,後序,這就跟我們運算值和運算符的擺放位置有關係了。
我一般都用的是中序,例如1+2,運算符在運算值中間,前序則是+12,後續則是12+,好吧,其實超簡單的,講我上面的代碼稍微改一下就好了。
1.3.11 跟運算式入棧基本無差,好吧
public void EvaluatePostfix(String s){
String[] strings = s.split("");
Stack<Integer> whole=new Stack<Integer>();
Stack<String> operaters=new Stack<String>();
String pattern="[0-9]";
String pattern1="(\\+|\\-|\\*|\\/)";
Pattern p=Pattern.compile(pattern);
Pattern p1=Pattern.compile(pattern1);
Matcher matcher;
Matcher matcher1;
for(int i=0;i<strings.length;i++){
//如果是0-9的話壓進全棧
//如果是+-*/就壓進運算符棧
matcher=p.matcher(strings[i]);
matcher1=p1.matcher(strings[i]);
if(matcher1.find()){
operaters.push(strings[i]);
continue;
}
if(matcher.find()){
whole.push(Integer.parseInt(strings[i]));
continue;
}
//如果是),whole彈兩個,運算符彈一個,加上括號組成真正的運算式
if(strings[i].equals(")")){
int n1=whole.pop();
int n2=whole.pop();
String o1=operaters.pop();
if(o1.equals("+")){
whole.push(n2+n1);
}
if(o1.equals("-")){
whole.push(n2-n1);
}
if(o1.equals("*")){
whole.push(n2*n1);
}
}
}
System.out.println(whole.pop());
}
1.3.12 利用迭代器,將數據取出,在塞進副本,完成在傳出來
public Stack<String> copy(Stack<String> s){
Stack<String> copy=new Stack<>();
Iterator<String> iterator=s.iterator();
while (iterator.hasNext()){
String next = iterator.next();
System.out.print(next);
copy.push(next);
}
return copy;
}
1.3.13 看1.3.3吧。
1.3.14 直接上代碼吧,因爲是數組實現的數組,所以節點就不需要next了
public class ResizingArrayQueueOfStrings {
class Node{
String s;
public Node(String s) {
this.s = s;
}
public String getS() {
return s;
}
public void setS(String s) {
this.s = s;
}
}
int size;
//隊頭和隊尾
int f=0;
int l=0;
public ResizingArrayQueueOfStrings(int size) {
this.size = size;
queue=new Node[size];
}
private Node[] queue;
//進隊列
public void push(String s){
if((l)==size){
resizing();
}
Node n=new Node(s);
queue[l]=n;
l++;
}
//出隊列
public void pop(){
System.out.println(queue[f]);
f++;
}
//每次擴容將容量X2,引用新的數組
private void resizing(){
size=size*2;
Node[] noeq=new Node[size];
for(int i=0;i<queue.length;i++){
noeq[i]=queue[i];
}
queue=noeq;
}
}
1.3.15 毫無技術難度,好吧
public String getdesc(int n){
if(n>(l-f)){
System.out.println("無該字符");
return "error";
}else {
return queue[l-n].s;
}
}
1.3.16 em。。。。
public class ResizingArrayQueueOfDates {
private Date[] queue;
int size;
//隊頭和隊尾
int f=0;
int l=0;
public ResizingArrayQueueOfDates(int size) {
this.size = size;
queue=new Date[size];
}
class Date {
int year;
int month;
int day;
public Date(String s) {
String[] datepart = s.split("/");
this.year = Integer.parseInt(datepart[0]);
this.month = Integer.parseInt(datepart[1]);
this.day = Integer.parseInt(datepart[2]);
}
}
public void input(String s){
String[] dates=s.split(" ");
for(String d:dates){
if((l)==size){
resizing();
}
Date date=new Date(d);
queue[l]=date;
l++;
}
}
//每次擴容將容量X2,引用新的數組
private void resizing(){
size=size*2;
Date[] noeq=new Date[size];
for(int i=0;i<queue.length;i++){
noeq[i]=queue[i];
}
queue=noeq;
}
}
1.3.17 跟上面那題基本一樣的思路就不整了
1.3.19
public void deLinkedListL(){
Node find=first;
//找鏈表last節點的前一個節點
while(find.next!=last){
find=find.next;
}
//將其的last設爲空,將其設爲last
find.next=null;
last=find;
System.out.println("刪除表尾節點完成");
}
1.3.20 直接看代碼吧
public void deleteN(int n){
Node del=first;
//比如我要刪除第五個,那我就要先讀到第四個,然後將第四個的後面隔一個賦值到第四個的next上,那就可以吧第五個跳過了,
// 然後GC就把第五個收走了,因爲鏈表從1開始,那就到一就刪。
while(n>1){
if(del.next!=null){
del=del.next;
n--;
}else{
System.out.println("節點不足");
return;
}
}
System.out.println(del.i);
del.next=del.next.next;
}
1.3.21 直接往下遍歷,有就返回true,若是最後都沒有找到就返回false
public boolean find(int key){
Node find=first;
while (find.next!=null){
if(find.i==key){
return true;
}
find=find.next;
}
return false;
}
1.3.24 這個用我上面寫的那個deleteN就能實現,n+1就好了,他說的拿個無需操作,那就忽視的我的sout吧。
1.3.25 先遍歷,找到之後,保存該節點的next,將新增節點插進去後,新增節點的next設爲保存的節點
public void insertAfter(int key,int node){
Node find=first;
while (find.next!=null){
if(find.i==key){
Node temp=find.next;
find.next=new Node(node);
find.next.next=temp;
return;
}
find=find.next;
}
}
1.3.26 遍歷,查看自己的next時候符合key,是的話就刪,不是繼續走
public void remove(int key){
Node find=first;
while(find.next!=null){
if(find.next.i==key){
find.next=find.next.next;
}
find=find.next;
}
}
1.3.27 遍歷,然後逐一比較,冒泡一遍求最大
public void getMax(){
Node find=first;
int max=find.i;
while (find.next!=null){
if(find.next.i>max){
max=find.next.i;
}
find=find.next;
}
System.out.println(max);
}
1.3.28 遞歸手法求出求上一問
public int getMax(Node n,int max){
if(n.i>max){
max=n.i;
}
if(n.next==null){
return max;
}
return getMax(n.next,max);
}
public void go(){
System.out.println(getMax(first,first.i));
}
1.3.29 將last的next設成first就好了,沒什麼技術難度。
1.3.31 這個將node設置成雙向的就好了,一個next指向下一個節點,一個last指向上一個節點。
1.3.32 Steque
public class Steque {
class Node{
int i;
Node next;
public Node(int i) {
this.i = i;
}
}
Node first;
Node last;
public Steque() {
Node n=new Node(0);
first=n;
last=n;
}
public Node pop(){
//將first傳給first.next,然後把first傳進去
if(first!=null){
Node p=first;
first=first.next;
return p;
}else {
System.out.println("鏈表爲空");
return null;
}
}
public void push(int i){
//將新的節點的next設成原來的first節點,然後將新的節點設成first節點
Node n=new Node(i);
n.next=first;
first=n;
}
public void enqueue(int i){
//把之前的尾節點的next賦值新進的節點,再把新節點設成尾節點
Node n=new Node(i);
last.next=n;
last=n;
}
}
1.3.33Deque的api,用數組太蠢了,每次表頭插入,得整個數組移動一位,表頭刪除又要數組向前一位,我用的直接引用的形式
public class Deque {
Node first;
Node end;
class Node{
int i;
Node next;
Node last;
public Node(int i) {
this.i = i;
}
}
public void push(int i){
Node n=new Node(i);
if(first==null){
first=n;
end=n;
}else {
first.last=n;
n.next=first;
first=n;
}
show();
}
public Node pop(){
if(first==null){
System.out.println("隊列沒東西,別鬧了");
return null;
}else {
Node n=first;
first.next.last=null;
first=first.next;
show();
return n;
}
}
public void pushEnd(int i){
Node n=new Node(i);
if(first==null){
first=n;
end=n;
}else {
end.next=n;
n.last=end;
end=n;
show();
}
}
public Node popEnd(){
if(first==null){
System.out.println("隊列沒東西,別鬧了");
return null;
}
Node n=end;
end.last.next=null;
end=end.last;
show();
return n;
}
void show(){
Node n=first;
do {
System.out.print("\t"+n.i);
n=n.next;
}while(n!=null);
System.out.println("\t"+"f---"+first.i+"\t"+"e---"+end.i);
}
}
1.3.34 隨機揹包,在迭代器裏將其亂序展示
public class RandomBag {
int[] bag=new int[100];
int size;
class Iterator{
int[] rBag;
int size;
public Iterator(int[] bag,int size) {
this.size=size;
Random random=new Random();
rBag=new int[size];
for(int i=0;i<size;i++){
rBag[i]=bag[i];
}
int temp=0;
for(int i=0;i<size;i++){
int r = random.nextInt(size);
temp=rBag[i];
rBag[i]=rBag[r];
rBag[r]=temp;
}
}
public void show(){
for(int i=0;i<size;i++){
System.out.print("\t"+rBag[i]);
}
System.out.println();
}
}
public boolean idEmpty(){
if(bag[0]==0){
return false;
}else {
return true;
}
}
public int size(){
return size;
}
public void add(int i){
bag[size]=i;
size++;
}
public void iteratorShow(){
new Iterator(bag,size).show();
}
}
1.3.35 隨機隊列
package chapter1_3;
import java.util.Random;
public class RandomQueue {
Card[] cards;
int size;
int last;
public RandomQueue(int size) {
this.size = size;
cards=new Card[size];
last=0;
}
public void resizing(int size){
this.size=size;
Card[] temp=new Card[size];
for(int i=0;i<last;i++){
temp[i]=cards[i];
}
cards=temp;
}
public void enqueue(String num, String huase){
Card c=new Card(num,huase);
//若果尾巴也有數據就擴張
if(cards[size-1]!=null){
resizing(size*2);
}
cards[last]=c;
last++;
}
public Card dequeue(){
Card c=cards[0];
last--;
for(int i=0;i<last;i++){
cards[i]=cards[i+1];
}
//如果中間也是空的就收縮
if(cards[(size/2)-1]==null){
resizing(size/2);
}
//抽卡的時候觸發置換,用末尾的話,初期發牌太有規律了,根本沒有起到洗牌的功能,所以我換成了頭部置換
Random random=new Random();
int r = random.nextInt(last);
Card temp=cards[r];
cards[r]=cards[0];
cards[0]=temp;
// System.out.println("r"+cards[r]);
// System.out.println("l"+cards[0]);
return c;
}
public Card sqmple(){
Random random=new Random();
int r = random.nextInt(last);
return cards[r];
}
public void start(){
String num;
String huase="";
for(int i=0;i<4;i++){
if(i==0){
huase="方塊";
}
if(i==1){
huase="梅花";
}
if(i==2){
huase="紅心";
}
if(i==3){
huase="黑桃";
}
for(int j=1;j<14;j++){
if(j==11){
num="J";
}else if(j==12){
num="Q";
}else if(j==13){
num="K";
}else {
num=""+j;
}
enqueue(num,huase);
}
}
}
}
1.3.36 這個我就不寫了,沒多大意思,數組遍歷唄
1.3.37 經典題目:約瑟夫環,我打算用一個環形的鏈表解決這個問題
public class Josephus {
Node start;
Node last;
int kill;
class Node{
int name;
Node next;
public Node(int name) {
this.name = name;
}
}
public Josephus(int size,int kill) {
this.kill=kill;
for(int i=0;i<size;i++){
enqueue(i+1);
}
}
public void enqueue(int name){
Node n=new Node(name);
//當前鏈表爲空的時候,將該節點設爲start
if(start==null){
start=n;
last=n;
n.next=n;
}else{
last.next=n;
n.next=start;
last=n;
}
}
public void startKill(){
int k;
Node getKilled=last;
while(true){
k=kill;
while (k>1){
//按着開槍間隔往後走
getKilled=getKilled.next;
k--;
}
System.out.println(getKilled.next.name);
if(getKilled.next==getKilled){
System.out.println("結束");
break;
}
//把要死的節點的前面的節點的next設爲死的節點的next,節點死掉
getKilled.next=getKilled.next.next;
}
}
}
1.3.38 說下思路吧,就不寫代碼了,先是鏈表,有個int類型標明進來過多少個節點,每個節點進入的時候都會有個int類型的數據,上面標明該節點是第幾進來的,關於這個delete方法,獲取了k之後,直接搜索隊列,int數據類型=k,直接拿出來。
1.3.39 環形緩存器
public class RingBuffer {
Node start;
Node last;
int capacity;
int now;
class Node{
public Node(int name) {
this.name = name;
}
Node next;
int name;
}
public RingBuffer(int capacity) {
this.capacity = capacity;
now=0;
}
void enqueue(int name){
Node n=new Node(name);
if(start==null){
start=n;
last=n;
n.next=n;
now++;
}else {
last.next=n;
n.next=start;
last=n;
now++;
}
if(now==capacity){
consume();
}
}
public void product(int i){
System.out.println("生產"+i+"個");
while(i-->0){
enqueue(now);
}
}
void consume(){
Node n=start;
System.out.println("啓動緩存消耗");
for(int i=0;i<capacity;i++){
System.out.print(n.name);
n=n.next;
}
System.out.println();
start=null;
last=null;
}
}
1.3.40 前移編碼,又是一個非常簡單的,不上代碼了,每次輸入的時候,進行鏈表節點逐個校對,無就存,有就刪掉,從頭的部分重新進入一次,以前做的find功能修改一下就可以用了。
1.3.41和1.3.42基本一致思路,就是將需要被複制的隊列或者棧,通過構造器,遍歷複製一遍就好了。
1.3.43 文件列表
public class FileList {
File[] files;
int p=0;
int maxl;
class File{
String name;
int level;
public File(String name, int level) {
this.name = name;
this.level = level;
}
}
public FileList() {
files=new File[100];
this.maxl=0;
}
public void search(String filePath,int level){
if(level>maxl){
maxl=level;
}
java.io.File f=new java.io.File(filePath);
java.io.File[] list = f.listFiles();
for (java.io.File s:list
) {
files[p]=new File(s.getName(),level);
p++;
}
for(java.io.File s:list){
if (s.isDirectory()){
search(s.getPath(),level+1);
}
}
}
public void show() {
int layer = maxl+1;
while (layer-- > 0) {
for (int i = 0; i < p; i++) {
if (files[i].level == maxl - layer) {
System.out.print("\t" + files[i].name+"\t");
}
}
System.out.println();
}
}
}
1.3.44 文本編輯緩衝區,說下思路吧,兩個棧來進行存儲,一個棧是光標左邊的字符,另一個棧是光標右邊的字符,光標往左移則右邊的棧出棧,出棧的字符入棧右邊,往右邊動則blabla,刪除的話,就左邊出棧,然後輸出出來,插入字符則是往左邊的棧進棧。
1.3.45 設定一個整型變量,進棧+1,出棧-1,一直往後度,整型變量一小於一,就返回報錯。
1.3.46 入棧問題,入棧的次序決定了出棧的先後,不管怎麼進其他的數據都一樣
1.3.47 連接隊列,一個方法獲取兩個隊列,將第二個隊列的數據出列,入列第二個隊列,最後得到一個鏈接完成的隊列
連接棧的話就麻煩點,其中一個棧的全部提取出來存在數組裏,在逆序壓入另一個棧
1.3.48 雙向隊列裏一開始有一個節點叫做bottom,左邊壓一個棧,右邊壓一個,取到碰到bottom則是棧已取空
1.3.49 嗯。。。。兩個棧,現將隊列所有數據壓入一棧,再出棧壓入二棧,要出列的話,則直接二棧出棧,要入隊列的話,就先將二棧所有出棧,入棧一棧,需要入隊列的的數據直接壓入一棧
1.3.50 一種樂觀鎖的手法,認爲一般不會有人改我的數據,但是上鎖被改過之後,就會報異常