《數(shù)據(jù)結(jié)構(gòu)與算法之美》筆記009

43. | 拓撲排序:如何確定代碼源文件的編譯依賴關(guān)系 - 開始

  1. 編譯器如何通過源文件兩兩之間的局部依賴關(guān)系,確定一個全局的編譯順序:
    使用 '圖' 這種數(shù)據(jù)結(jié)構(gòu)的 '拓撲排序算法' 解決.
  2. 什么是拓撲排序
  • 多個元素,部分或全部元素的兩兩依賴關(guān)系已經(jīng)確定,如何安排一個序列,能夠滿足上述元素的兩兩依賴關(guān)系.
  • 很多時候,拓撲排序的序列不是唯一的,因為可能不是所有元素之間都具有依賴關(guān)系.


    c26d0f472d9a607c0c4eb688c01959bd[1].jpg
  1. 拓撲排序的原理很簡單,重要的是拓撲排序的實現(xiàn).
  2. 如何確定代碼源文件的編譯依賴關(guān)系:
  • 把源文件和源文件之間的依賴關(guān)系,抽象成1個有向圖;
  • 每個源文件對應(yīng)圖中的一個頂點;
  • 源文件之間的依賴關(guān)系就是頂點之間的邊;
  • 如果A先于B執(zhí)行,或者說B依賴于A,就在A和B之間,構(gòu)建一條從A指向B的的邊;
  • 注意這個圖必須是一個有向無環(huán)圖. 因為圖中一旦成環(huán),拓撲排序就無法工作;
  • 拓撲排序本身就是基于有向無環(huán)圖的一個算法.
public class Graph {
  //定義頂點的個數(shù)
  private int v;
  //每個頂點對應(yīng)一個鄰接表, 多個頂點構(gòu)鄰接表數(shù)組
  private LinkeList<Integer>[] adj;
  //構(gòu)造函數(shù)
  public Graph(int v){
    this.v = v;
    adj = new LinkedList<Integer>[v];
    for(int i=0; i<v; i++){
      adj[i] = new LinkedList<Integer>();
    }
  }
  
  //s優(yōu)先于t / t依賴于s
  //從頂點s出發(fā),構(gòu)建一條指向t的邊
  public void addEdge(int s, int t){
    adj[s].add(t);
  }
}
  1. 如何在Graph這個有向無環(huán)圖上,實現(xiàn)拓撲排序,有2種算法: Kahn算法 和 DFS深度優(yōu)先搜索算法.
  2. 看Kahn算法 和 DFS深度優(yōu)先搜索算法 之前,首先回顧下之前的'圖'相關(guān)知識.

43前置知識: 圖

  • 圖是一種非線性表數(shù)據(jù)結(jié)構(gòu).
  • 圖中的每個元素叫做頂點.
  • 圖中的1個頂點可以與其他任意頂點進行連接,連接叫做邊.
  • 有向圖:
    每條邊都是有方向的.A->B:從A到B. B->A:從B到A.
  • 無向圖:
    每條邊都是沒有方向的.單純代表A-B兩個頂點建立連接.
  • 每個頂點有多少條邊,叫做頂點的度.
  • 有向圖中,每個頂點有 入度 和 出度.
    • 入度:有多少條邊指向該頂點;
    • 出度:從該頂點發(fā)出多少條邊;


  1. 帶權(quán)圖
  • 每條邊都有1個權(quán)重/weight.


    帶權(quán)圖
  1. 圖的存儲方法: 鄰接矩陣 和 鄰接表.
  2. 使用 鄰接矩陣 存儲圖
  • 鄰接矩陣底層是1個二維數(shù)組.
  • 對于有向圖: 頂點i向頂點j發(fā)出一條邊 , 則 A[i][j] = 1;
  • 對于無向圖: 頂點i和頂點j之間有邊,則 A[i][j] 和 A[j][i] 都是1;
  • 對于帶權(quán)圖: 數(shù)組元素存儲連接的2個頂點之間的權(quán)重;


    使用鄰接矩陣存儲圖

    練一下markdown中怎么畫矩陣.

$$\begin{matrix}
A & B & C\\
D & E & F\\
G & H & I\\
\end{matrix}$$

無向圖:
\begin{matrix} 0 & 1 & 1 & 0\\ 1 & 0 & 1 & 1\\ 1 & 1 & 0 & 1\\ 0 & 1 & 1 & 0\\ \end{matrix}
有向圖:
\begin{matrix} 0 & 1 & 0 & 0\\ 0 & 0 & 1 & 0\\ 1 & 1 & 0 & 0\\ 0 & 1 & 1 & 0\\ \end{matrix}
帶權(quán)圖:
\begin{matrix} 0 & 5 & 3 & 0\\ 5 & 0 & 2 & 6\\ 3 & 2 & 0 & 1\\ 0 & 6 & 1 & 0\\ \end{matrix}

  • 鄰接矩陣的缺點: 浪費存儲空間.
    • 對于無向圖,頂點i,j之間有邊,需要A[i][j]和A[j][i]都存儲為1,實際只存1個就夠了.浪費了一半空間;
    • 對于稀疏圖.即頂點很多,但每個頂點的邊不多,使用鄰接矩陣就更加浪費存儲空間.
      • 比如微信十億用戶,但每個用戶最多2000個好友.使用鄰接矩陣會浪費絕大部分存儲空間.
  • 鄰接矩陣的有點:
    • 鄰接矩陣基于數(shù)組,獲取2個頂點的關(guān)系非常快;
    • 臨街矩陣存儲圖可以將很多圖的運算,轉(zhuǎn)換為矩陣之間的運算,效率很高.
  1. 使用 鄰接表 存儲圖


    使用鄰接表存儲圖
  • 鄰接表存儲圖很像散列表.
  • 每個頂點對應(yīng)1條鏈表,存儲與該頂點相連的其他頂點.
  • 鄰接表存儲圖 和 鄰接矩陣存儲圖 比較:
    • 相對于鄰接數(shù)組節(jié)省空間,尤其是稀疏圖場景.只有相連的頂點的邊才會存儲在對應(yīng)頂點的鏈表中.
    • 是用時間換空間,比如查詢頂點A和頂點B是否相連,就要在A頂點對應(yīng)的鏈表中遍歷查詢.
    • 鏈表的存儲對緩存不友好,所以鏈表中查詢兩個頂點之間的關(guān)系效率低.
    • 可以將鄰接表中的鏈表替換為支持快速查找的數(shù)據(jù)結(jié)構(gòu): 紅黑樹、跳表、有序動態(tài)數(shù)組,散列表
  1. 使用圖,存儲微博的用戶之間關(guān)系
    針對微博用戶關(guān)系,假設(shè)我們需要支持下面這樣幾個操作:判斷用戶 A 是否關(guān)注了用戶 B;判斷用戶 A 是否是用戶 B 的粉絲;用戶 A 關(guān)注用戶 B;用戶 A 取消關(guān)注用戶 B;根據(jù)用戶名稱的首字母排序,分頁獲取用戶的粉絲列表;根據(jù)用戶名稱的首字母排序,分頁獲取用戶的關(guān)注列表。
public class Graph {
  //微博系統(tǒng)中用戶量
  private int v;
  //BestType用于指代特定的數(shù)據(jù)結(jié)構(gòu),用于替換鏈表,提升鏈表的查詢效率
  //followers用于存儲每個用戶關(guān)注的人
  private BestType<Integer>[] followers;
  //fans用戶存儲每個用戶被哪些人關(guān)注
  private BestType<Integer>[] fans;
  public Graph(int v){
    this.v = v;
    followers = new BestType<Integer>[v];
    fans= new BestType<Integer>[v];
    for(int i=0; i<v; i++){
      followers[i] = new BestType<Integer>();
      fans[i] = new BestType<Integer>();
    }
  }
  //用戶a關(guān)注用戶b
  public void follow(User a, User b){
    Integer aIndex = hash(a);
    Integer bIndex = hash(b);
    Integer aId = a.userId;
    Integer bId = b.userId;
    //將用戶b的id添加到用戶a對應(yīng)的 關(guān)注的人 中
    followers[aIndex].add(bId);
    //將用戶a的id添加到用戶b對應(yīng)的 粉絲 中
    fans[bIndex].add(aId);
  }
  //用戶a不再關(guān)注用戶b
  public void unfollow(User a,User b){
    Integer aIndex = hash(a);
    Integer bIndex = hash(b);
    Integer aId = a.userId;
    Integer bId = b.userId;
    //將用戶b的id從用戶a對應(yīng)的 關(guān)注的人 中刪除
    followers[aIndex].remove(bId);
    //將用戶a的id從用戶b對應(yīng)的 粉絲 中刪除
    fans[bIndex].remove(aId);
  }
  
  //BestType具體選擇哪一種支持快速查詢的數(shù)據(jù)結(jié)構(gòu): 跳表 .
  //跳表插入、刪除、查找都非常高效,時間復(fù)雜度是 O(logn),空間復(fù)雜度上稍高,是 O(n)。
  //最重要的一點,跳表中存儲的數(shù)據(jù)本來就是有序的了,分頁獲取粉絲列表或關(guān)注列表,就非常高效
}
  1. 深度優(yōu)先搜索算法 和 廣度優(yōu)先搜索算法 , 都是基于 '圖' 這種數(shù)據(jù)結(jié)構(gòu).
  2. 廣度優(yōu)先搜索/BFS/Breadth-First-Search
public class Graph { // 無向圖
  private int v; // 頂點的個數(shù)
  private LinkedList<Integer> adj[]; // 鄰接表

  public Graph(int v) {
    this.v = v;
    adj = new LinkedList[v];
    for (int i=0; i<v; ++i) {
      adj[i] = new LinkedList<>();
    }
  }

  public void addEdge(int s, int t) { // 無向圖一條邊存兩次
    adj[s].add(t);
    adj[t].add(s);
  }
}
  • 廣度優(yōu)先搜索,就是先查找離起始頂點最近的,然后是次近的,一層層往外搜索.
  • 從頂點s出發(fā),搜索一條從s到t的最短路徑.
private void bfs(int s, int t){
  if(s == t){
    //同一頂點不用繼續(xù)搜索
    return;
  }
  //記錄指定頂點是否訪問過
  boolean[] visited = new boolean[v];
  visited[s] = true;
  //保存每個訪問過的頂點的前一頂點
  int[] prev = new int[v];
  for(int i=0;i<v;i++){
    prev[i] = -1;
  }
  //保存待比較其后續(xù)頂點的頂點
  Queue<Integer> queue = new LinkedList<Integer>();
  queue.add(s);
  while(queue.size() > 0){
    //獲取當前頂點
    int curr = queue.poll();
    //遍歷比較當前頂點的所有子頂點
    for(int i = 0; i<adj[curr].size(); i++){
      int c = adj[curr].get(i);
      if(!visited[c]){
        //標記當前子頂點 已被訪問過
        visited[c] = true;
        //標記當前子頂點 的上一結(jié)點就是curr
        prev[c] = curr;
        //如果當前子頂點 就是 要尋找的最終頂點
        if(c == t){
          //打印整個搜索路徑, 終止循環(huán)
          printRouter(prev, s, t);
          return;
        }
        queue.add(c);
      }
    }
  }
}
private void printRouter(int[] prev, int start, int target){
  if(prev[target] != -1 && target != start){
    //繼續(xù)打印 start 到 target之前1個元素的路徑
    printRouter(prev, start, prev[target]);
  }
  //打印當前元素
  System.out.println("當前元素:" + target);
}
  • 廣度優(yōu)先搜索算法的 時間復(fù)雜度,空間復(fù)雜度:
    • 時間復(fù)雜度是O(V+E)
      • V表示頂點的個數(shù),E表示邊的個數(shù)
      • 對于1個連通圖.即一個圖中所有的頂點都是連通的圖,E肯定大于V
      • 所以廣度優(yōu)先搜索算法的時間復(fù)雜度簡寫為O(E)
    • 空間復(fù)雜度的O(V)
      • visited 數(shù)組,prev 數(shù)組,queue隊列,這三個存儲空間大小都不會超過頂點個數(shù)V.
      • 所以其空間復(fù)雜度是O(V).
  1. 深度優(yōu)先搜索 / DFS / Depth-First-Search
  • 深度優(yōu)先搜索算法,本質(zhì)上就是回溯算法
  • 每一步,都要n種走法,每一種走法都走一遍
  • 回溯算法的套路
1: 整體問題最終值 存放在回溯方法體外;
2: 回溯方法中有判斷終止條件 及 整體問題最終值修改邏輯;
3: 回溯方法中:
每一步都有n種走法;
都走一遍;

套路:
int bestValue = -1;
private void recur(int param, int bound){
  if(shouldReturn(param, bound)){
    if(shouldModifBest(param,bound)){
      bestValue = **;
    }
    return;
  }
  //n種走法
  recur(p1,bound);
  ***
  recur(pn,bound);
}

調(diào)用方式:
int bestValue = -1;
recur(p1.bound);
sys: 最優(yōu)解: + bestValue.
  • 使用深度優(yōu)先搜索算法尋找頂點s到頂點t的路徑
private boolean found = false;
private void recurDfs(int curr, int target, boolean[] visited, int[] prev){
  if(found){
    //之前已經(jīng)找到路徑,避免重復(fù)執(zhí)行
    //終止條件
    return;
  }
  visited[curr] = true;
  if(curr == target){
    //終止條件
    //修改方法體外的變量
    found = true;
    return;
  }
  //獲取n條可能走的路徑
  for(int i=0; i<adj[curr].size(); i++){
    int c = adj[curr].get(i);
    if(!visited[c]){
      //之前沒有遍歷過該子頂點
      prev[c] = curr;
      //每一條可以走的路徑,都走一次
      recurDfs(c, target, visited,prev);
    }
  }
}
private testDFS(int s, int t){
  found = false;
  boolean[] visited = new boolean[v];
  int[] prev = new int[v];
  for(int i=0;i<v;i+){
    prev[i] = -1;
  }
  //回溯算法
  recurDfs(s, t, visited, prev)
  //打印路徑
  printRouter(prev, s, t);
}
  • 深度優(yōu)先搜索算法/回溯算法 的時間復(fù)雜度 和 空間復(fù)雜度
    • 遍歷 + 回溯, 每條邊最多訪問2次: parent -> n個child; 每個child -> 包括parent在內(nèi)的n個頂點.
    • 時間復(fù)雜度是O(E). E是邊數(shù)量
    • 空間復(fù)雜度是O(V). 因為額外的visited ,prev 數(shù)量不超過V.

43. | 拓撲排序:如何確定代碼源文件的編譯依賴關(guān)系 - 繼續(xù)

public class Graph{
  private int v;
  private LinkedList<Integer>[] adj;
  public Graph(int v){
    this.v = v;
    this.adj = new LinkedList<Integer>[v];
    for(int i=0;i<v;i++){
      adj[i] = new LinkedList<Integer>();
    }
  }
  //添加從頂點s到頂點t的一條邊
  //因為是依賴關(guān)系,所以是有向圖
  public void addEdge(int s, int t){
    adj[s].add(t);
  }
}

如何在有向無環(huán)圖上,實現(xiàn)拓撲排序.
拓撲排序有2種實現(xiàn)方法: Kahn算法 及 DFS深度優(yōu)先搜索算法.

  1. Kahn算法
  • Kahn算法實際用的是貪心算法思想
  • 頂點的入度為0,說明該頂點不依賴任何其他頂點,可以立即執(zhí)行.
  • Kahn實現(xiàn)步驟:
    • 定義1個數(shù)組,統(tǒng)計圖中所有頂點的入度,存儲于該數(shù)組
    • 定義1個隊列,存儲圖中所有入度為0的頂點
    • 從隊列頭部不斷取入度為0的頂點,執(zhí)行/打印.
      • 將該頂點對應(yīng)的所有子頂點的入度-1
      • 若某個子頂點的入度變?yōu)?,則將該子頂點也加入隊列
  • Kahn代碼實現(xiàn)
public void topoSortByKahn(){
  int[] inDegree = new int[v];
  for(int i = 0; i<v; i++){
    for(int j=0;j<adj[i].size();j++){
      int curr = adj[i].get(j);
      inDegree[curr]++;
    }
  }
  Queue<Integer> q = new LinkedList<Integer>();
  for(int i=0;i<v;i++){
    if(inDegree[i] == 0){
      q.add(i);
    }
  }
  int curr;
  int child;
  while(!q.isEmpty()){
    curr = q.poll();
    System.out.println("當前編譯:" + curr);
    for(int i=0;i<adj[curr].size();i++){
      //遍歷當前頂點的所有子頂點
      child = adj[curr].get(i);
      //每個子頂點的入度-1
      inDegree[child ]--;
      //若子頂點入度變?yōu)?
      if(inDegree[child] == 0){
        q.add(child);
      }
    }
  }
}
  1. DFS算法
  • DFS算法本質(zhì)上是回溯算法
  • 原始數(shù)據(jù)是 頂點->多個子頂點
  • 要答應(yīng)整個依賴關(guān)系,需要知道每個頂點都依賴哪些父頂點, 根據(jù)原始的 curr->children 構(gòu)建逆鄰接表 curr->parents
  • 每次打印1個頂點,都要先保證其 parents 都打印完畢,再打印自身.
  • DFS代碼實現(xiàn)
public void dfsDeps(){
  boolean[] visited = new boolean[v];
  LinkedList<Integer>[] parents = new LinkedList<Integer>[v];
  for(int i =0;i<v;i++){
    parents[i] = new LinkedList<Integer>();
  }
  int child;
  for(int i=0;i<adj.length;i++){
    for(int j=0;j<adj[i].size();j++){
      child = adj[i].get(j);
      parents[child].add(i);
    }
  }
  //遍歷parents,將每個頂點都打印一遍
  for(int i=0;i<v;i++){
    if(!visited[i]){
      printCurr(i,visited,parents);
    }
  }
}
private void printCurr(int curr,boolean[] visited,LinkedList<Integer>[] parents){
  if(visited[curr]){
    return;
  }
  //標記當前頂點已訪問過
  visited[curr] = true;
  int parent;
  //獲取當前頂點所有父頂點
  for(int i =0; i<parents[curr].size(); i++){
    parent = parents[curr].get(i);
    //打印父頂點
    printCurr(parent,visited,parents);
  }
  //所有父頂點打印完畢,再打印自身
  sysout: 當前編譯項為: + curr;
}
  1. kahn算法的時間復(fù)雜度O(E+V)/每個頂點被訪問1次,每條邊也被訪問1次, Kahn的空間復(fù)雜度O(V);
  2. DFS算法的空間復(fù)雜度是O(V),時間復(fù)雜度是O(E+V)/每個頂點被訪問2次,每條邊被訪問1次.
  • 每個頂點被訪問2次: curr -> parents; parent -> parents;
  1. 拓撲排序的應(yīng)用場景: 凡是需要通過局部順序來推導(dǎo)全局順序的,一般都能用拓撲排序來解決.
  2. 檢測圖中是否存在環(huán):
    6.1: Kahn算法,如果最終打印的頂點個數(shù)數(shù)量少于頂點總量,說明圖中成環(huán),部分頂點始終無法達到入度為0
    6.2: 如果是A推薦B,B推薦C,C推薦D,D推薦E,E又推薦A這樣的環(huán)狀結(jié)構(gòu),如何檢測.
    只要從A開始向下遞推,每個訪問過的人都記錄下來,如果成環(huán),同1個人會被訪問2次.
    A->B->C->D->E->A.
    只要記錄中出現(xiàn)1個人被訪問2次,說明成環(huán)了.
private HaseSet<Integer> visited = new HashSet<Integer>();
private int findFinalRef(int userId){
  if(visited.contains(userId)){
    //說明同一用戶被訪問第2次,說明出現(xiàn)了環(huán)狀結(jié)構(gòu)
    return;
  }
  int refId = gainCurrUserRef(userId);
  if(refId == -1){
    //說明當前userId沒有下一個推薦人,則返回自身
    return userId;
  }
  return findFinalRef(refId);
}

44. | 最短路徑:地圖軟件是如何計算出最優(yōu)出行路徑的

  1. Dijkstra算法相關(guān)文章
  1. 計算地圖上兩個地點的最優(yōu)路徑,可以將地圖抽象成'圖'這種結(jié)構(gòu).每個地點是圖的1個頂點.兩個地點之間的路是一條邊,單向路就是A->B,雙向路就是 A->B + B->A.
    路的長度,就是每條邊的權(quán)重.
    地圖就被抽象成1個有向有權(quán)圖.
  2. 地圖中兩個地點的最短路徑,變成 有向有權(quán)圖中2個頂點間的最短距離.
  3. 將地圖抽象成圖的代碼
public class Graph{
  //鄰接表
  private LinkedList<Edge>[] adj;
  //頂點個數(shù)
  private int v;
  public Graph(int v){
    this.v = v;
    this.adj = new LinkedList<Edge>[v];
    for(int i =0;i<v;i++){
      adj[i] = new LinkedList<Edget>();
    }
  }
  //添加一條邊. 從s到t.
  public void addEdge(int s,int t, int w){
    this.adj[s].add(new Edge(s, t, w));
  }
  private class Edge{
    public int sid; //邊的起始頂點編號
    public int tid; //邊的終止頂點編號
    public int w; //邊的權(quán)重
    public Edge(int sid, int tid, int w){
      this.sid = sid;
      this.tid = tid;
      this.w = w;
    }
  }
  //這個類專門為dijkstra算法實現(xiàn)的
  private class Vertex{
    //頂點id
    public int id;
    //從起始頂點 到這個頂點的 最短距離
    public int dist;
    public Vertex(int id, int dist){
      this.id = id;
      this.dist = dist;
    }
  }
}

一個頂點到另一個頂點的最短路徑算法,最著名的是Dijkstra算法.

  1. Dijkstra算法實現(xiàn)
//Java提供的優(yōu)先級隊列,沒有提供更新單個元素的接口,所以需要自行實現(xiàn)1個
private class PriorityQueue{
  //需要根據(jù)Vertex.dist構(gòu)建小頂堆
  private Vertex[] nodes;
  private int count;
  private PriorityQueue(int v){
    this.nodes = new Vertex[v + 1];
    this.count = v;
  }
  //待實現(xiàn)
  public Vertex poll(){}
  //待實現(xiàn)
  public void add(Vertex vertex){
  }
  //待實現(xiàn): 更新結(jié)點的值,并且從下往上堆化, 使之重新符合小頂堆的定義
  public void update(Vertex vertex){
  }
  //待實現(xiàn)
  public boolean isEmpty(){
  }
}

//dijkstra算法實現(xiàn)
public void dijkstra(int s,int t){
  int[] pre = new int[v];
  Vertex[] vertexes = new Vertex[v];
  for(int i=0;i<v;i++){
    vertexes[i] = new Vertex(i,Integer.MAX_VALUE);
  }
  PriorityQueue queue = new PriorityQueue(v);
  boolean[] inqueue = new boolean[v];
  queue.add(vertexes[s]);
  inqueue[s] = true;
  while(!queue.isEmpty()){
    Vertex minVertex = queue.poll();
    if(minVertex.id == t){
      break;
    }
    for(int i=0;i<adj[minVertex.id].size();i++){
      Edge e = adj[minVertex.id].get(i);
      Vertex nextVertex = vertexes[e.tid];
      if(minVertex.dist + e.w < nextVertex.dist){
        nextVertex.dist = minVertex.dist + e.w;
        pre[nextVertex.id] = minVertex.id;
        if(inqueue[nextVertex.id]){
          queue.update(nextVertex);
        }else{
          queue.add(nextVertex);
          inqueue[nextVertex.id] = true;
        }
      }
    }
  }
  System.out.println("從s到t最短路徑長度:" + vertexes[t].dist);
  //依次答應(yīng)最短路徑長度對應(yīng)的每個頂點
  print(s, t, pre);
}
private void print(int s, int t, int[] pre){
  if(s == t){
    System.out.println("當前元素:" + t);
    return;
  }
  print(s, pre[t], pre);
  //打印t
  System.out.println("當前元素:" + t);
}
  1. Dijkstra的時間復(fù)雜度
  • while循環(huán)次數(shù): 最多是V次, V代表頂點的個數(shù).
  • while循環(huán)內(nèi),每次for循環(huán)次數(shù)不同,V次總體for循環(huán)次數(shù) <= E. E是圖中邊的數(shù)量.
  • 優(yōu)先級隊列是1個小頂堆,每次poll,add或update,時間復(fù)雜度是O(logV)
  • 整個Dijkstra的時間復(fù)雜度是 O(E*logV)
  1. 地圖軟件用的更多的是類似 A* 的啟發(fā)式搜索算法,不過也是在 Dijkstra 算法上的優(yōu)化.

45 | 位圖:如何實現(xiàn)網(wǎng)頁爬蟲中的URL去重功能?

前置知識

  1. 位運算
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容