【數據結構&算法】10-串基礎&KMP算法源碼



前言

李柱明博客:https://www.cnblogs.com/lizhuming/p/15487367.html

串的定義

定義:

  • 串(string):由零個或多個字符組成的有限序列,又名叫字符串。

相關概念:

  • 空格串:只包含空格的串。

    • 注意:與空串區別,空格串是有內容有長度的,而且可以不止一個空格。
  • 子串:串中任意個數的連續字符組成的子序列,稱為該串的子串。

  • 主串:相應地,包含子串的串,稱為主串。

  • 子串在主串中的位置:子串的第一個字符在主串中的序號。

串的比較

串的比較

  • 通過組成串的字符之間的編碼來進行的。
  • 而字符的編碼:指的是字符在對應字符集中的序號。

ASCII 和 Unicode

  • ASCII 碼:用 8 個二進制數表示一個字符,總共可以表示 256 個字符。
  • Unicode 碼:用 16 位二進制數表示一個字符,總共有 2 的 16 次方 個字符。
  • 為了和 ASCII 碼兼容,Unicode 碼的前 256 個字符與 ASCII 碼完全相同。

串相等

  • 長度相等,各個對應位置的字符相等。

串的抽象類型數據

串與線性表的比較

線性表:更關注單個元素的操作,如查找一個元素,插入或刪除一個元素。

串:更多是查找子串位置、得到指定位置子串、替換子串等操作。

串的數據

數據:串中元素僅由一個字符組成,相鄰元素具有前驅和后繼關系。

操作:

  • str_assign(t, *cahrs); 生成一個其值等於字符串常量 chars 的串 t。
  • str_copy(t, s); 串 s 存在,由串 s 復制得到串 t 中。
  • str_clear(s); 清空串。
  • str_empty(s); 判斷串是否空。
  • str_length(s); 串的長度。
  • str_compare(s, t); 若 s>t,返回 >0 , 若 s=t ,返回 0 ,若 s<t ,返回 <0。
  • str_concat(t, s1, s2); 合並 s1 和 s2,通過 t 返回。
  • str_get_sub(t, s, pos, len); 在串 s 中從 pos 點開始截取最大 len 的長度,通過 t 返回。
  • str_index(s, t, pos); 在主串 s 的 pos 位置起查找子串 t 並返回起始子串起始位置,沒有則返回 0。
  • str_replace(s, t, v); 在主串 s 中查找子串 t,並用串 v 代替。
  • str_insert(s, pos, t); 在主串 s 的 pos 位置中插入串 t。
  • str_delete(s, pos, len); 在主串 s 中的 pos 位置其刪除長度為 len 的子串。

串的存儲結構

串的存儲結構與線性表類似,分為兩類:順序和鏈式

串的順序存儲結構

定義:用一組地址連續的存儲單元來存儲串中的字符序列。

按照預定義大小,為每個定義的串分配一個固定長度的存儲區,一般用定長數組來定義。

一般可以將實際的串長值保存在數組的 0 下標位置,或者在數組的最后一個下標位置。

但有的語言規定在串值后面加一個不計入串長度的結束標記符號“\0”來表示串值的終結(但占用一個空間)。

由於過於不便,串的順序存儲操作有一些變化:串值的存儲空間可在程序執行過程中動態分配而得

  • 比如堆:可由 c 語言動態分配函數 malloc() 和 free()來管理。

串的鏈式存儲結構

定義:用節點保存串的數據。

若一個結點存放一個字符,會存在很大的空間浪費。

故串的鏈式可以一個結點放多個字符,最后一個結點若不滿,可用#或其他非串值字符補全。(每個節點固定長度)

優點:連接兩串操作方便。

缺點:靈活度、性能都不如順序存儲結構的。

朴素的模式匹配算法

模式匹配的定義

子串(又稱模式串)的定位操作通常稱做串的模式匹配,是串中最重要的操作之一。

朴素的匹配方法(BRUTE FORCE 算法,BF 算法)

邏輯思路:

  • 對主串的每個字符作為子串開頭,與要匹配的字符串進行匹配。
  • 對主串做大循環,每個字符開頭做要匹配子串的長度的小循環,直到匹配成功或全部遍歷完成為止。

數據結構:

typedef struct{
    char *str;
    int max_length;
    int length;
}data_str_t;

代碼實現:

int bf_index(data_str_t main_str, int start, data_str_t sub_str)
{
	int i = start, j = 0, v;

	while ((i < main_str.length)&&(j < sub_str.length))
	{
		if(main_str.str[i] == sub_str.str[j])
		{
			i++;
			j++;
		}
		else
		{
			i = i - j + 1;
			j = 0;
		}
	}

	if (j == sub_str.length)
	{
		v = i-sub_str.length;
	}
	else
	{
		v = -1;
	}

	return v;
}

時間復雜度分析

n:主串長度,m:要匹配子串長度。

時間復雜度分析:

  • 最好情況:O(1)

    • 第一次比較就找到。
  • 平均情況:O(n+m)

    • 根據等概率原則,平均是(n+m)/2 次查找。
  • 最壞的情況: O(m×n) (注:(n-m+1)×m)

    • 每遍比較都在最后出現不等,即每遍最多比較 m 次,最多比較 n-m+1 遍,總的比較次數最多為 m(n-m+1)。

KMP 模式匹配算法

KMP 與 BF 算法

KMP 算法:

  • 由三位前輩發表的一個模式匹配算法,可以大大避免重復遍歷的情況,稱之為克努特-莫里斯-普拉特算法,檢查 KMP 算法。
  • 又叫 快速模式匹配算法

KMP 算法相比於 BF 算法,優勢在於:

  • 在保證指針 i 不回溯的前提下,當匹配失敗時,讓模式串向右移動最大的距離;
  • 並且可以在 O(n+m) 的時間數量級上完成對串的模式匹配操作。

KMP 算法原理

參考鏈接:CSDN

原理:

  • 主串 S 與模式串 T 有部分相同子串時,可以簡化朴素匹配算法中的循環流程。

  • KMP 中的關鍵就是求公共最長匹配前綴和后綴的長度。

    • 從子串最長前綴和最長后綴開始求。最長也少於前面字符個數。

    • 最長公共前綴的后面一個字符(指針 j)和匹配失敗的那個字符(指針 i)進行對比。

      • 若匹配相同,則繼續推薦 i 和 j。
      • 若匹配不同,則繼續縮短公共最長前綴和后綴。就是指針 j 進行參考 next 數組回溯。
  • 例子 1,如下圖:跳過主串和子串相同的部分。

    • 前提:要先知道模式串 T 中首字符 ‘a’ 與串 T 后面的字符均不相等。
  • 例子二,如下圖:跳過子串中與首字符相同的字符。

模式串向右移動距離的計算

在模式串和主串匹配時,各有一個指針指向當前進行匹配的字符(主串中是指針 i ,模式串中是指針 j )。

在保證 i 指針不回溯的前提下,如果想實現功能,就只能讓 j 指針回溯

j 指針回溯的距離,就相當於模式串向右移動的距離。 j 指針回溯的越多,說明模式串向右移動的距離越長。

計算模式串向右移動的距離,就可以轉化成:當某字符匹配失敗后, j 指針回溯的位置。

模式串中的每個字符所對應 j 指針回溯的位置,可以通過算法得出,得到的結果相應地存儲在一個數組中(默認數組名為 next )。

  • 即是模式串中遇到某個字符匹配失敗,就在 next 數組中找到對應的回溯位置,取出該位置對應的字符和當前的字符繼續匹配,直至全部匹配失敗再推進主串指針 i 和模式串指針 j。

計算方法:

  • 對於模式串中的某一字符來說,提取它前面的字符串,分別從字符串的兩端查看連續相同的字符串的個數,在其基礎上 +1 ,結果就是該字符對應的值。

  • 注意:

    • 字符對應 next 是第 0 個字符對應 next 數組下標為 1 開始的。
    • 前面兩個字符對應的回溯值為 0、1。

例子:求模式串 “abcabac” 的 next 。

  • 第 1 個字符:‘a’,next 值為 0。
  • 第 2 個字符:‘b’,next 值為 1。
  • 第 3 個字符:‘c’,提取字符串 ‘ab’,連續系統字符為 0 個。0 + 1 = 1。 next 值為 1。
  • 第 4 個字符:‘a’,提取字符串 ‘abc’,連續系統字符為 0 個。0 + 1 = 1。 next 值為 1。
  • 第 5 個字符:‘b’,提取字符串 ‘abca’,連續系統字符為 1 個。1 + 1 = 2。 next 值為 2。
  • 第 6 個字符:‘a’,提取字符串 ‘abcab’,連續系統字符為 2 個。2 + 1 = 3。 next 值為 3。
  • 第 7 個字符:‘c’,提取字符串 ‘abcaba’,連續系統字符為 1 個。1 + 1 = 2。 next 值為 2。
  • 由上的 next 數組的值為(從下標為 1 開始) [0, 1, 1, 1, 2, 3, 2]。

代碼實現

  • 理解:下圖為 demo

    • 理解字符串和 next 數組的下標都為 1 開始。(可以從 0 開始,自己留意下就可以了,不影響原理)

    • 計算 next 數組的值只需要模式串即可,求出每個字符匹配失敗時指針 j 回溯的長度。

    • 在求 next 數組的值得過程中:

      • 不要采用暴力算法檢索模式串。

        • 即是不要從可能最長的公共前后綴開始一個減一個地對比下去。如求圖中 j+1 的 next 值時,暴力算法就是對比 aabcaabcaaabcaabcaab,如果失敗就減少一個長度繼續重新對比 aabcaabcabcaabcaab。然后循環下去。
      • 應該采用 KMP 算法(對,就是在 KMP 算法中利用 KMP 算法思維)檢索模式串。

        • 即是如求圖中 j+1 的 next 值時,直接取出 j 的 next 值 k 對應的字符 b(j 匹配時的最長公共前后綴,從最長的公共前后綴下手),對比 j 和 k 對應的字符。

          • 如匹配相同,則 k+1 (在上一個字符的最長公共前后綴基礎上在加長一個字符)就是 j+1 對應字符的 next 值。
          • 若匹配不同,噢,匹配不同,后綴和前綴匹配時不同噢,主串和模式串匹配時不同噢,那就找出后綴(主串)和前綴(模式串)的公共前后綴,在這里前綴(主串)和后綴(模式串)是一樣的,就是找出前綴(模式串)的公共前后綴部分(或者說 k 匹配失敗時怎么辦,利用 next 回溯啊)。就是找出 k 對應的公共前后綴部分,我們已經求出來了啊,就是 next[k] k'。k' 對應的字符和 j 對應的字符繼續對比,若匹配相同,就 next[j] = k'+1,若匹配不同就繼續縮短最長公共前后綴,最長就是 k' 對應字符的最長公共前后綴 next[k']
          • 不說了,這樣應該能看出遞歸了吧。直至最長公共前后綴為 0 時,采用特殊處理,因為這里下標 0 沒有對應的字符,所以就推進 i 和 j,就是模式串的首字符和主串的下一個字符繼續比較。看代碼吧,騷年!!
    • 利用上一個字符對應的 next 值,取出對應的字符,比較當前字符。

      1. 如果相等。當前字符的 next 值為上一個 next 值 + 1。結束。

      2. 如果不等,就拿上一個 next 的值做 next 的下標,繼續取出字符對比。直至字符相等或循環到 next[1] = 0 結束。

        1. 循環獲取下標 j 就是不斷回溯 j。
  • 注意:

    • next 數組使用的下標初始值為 1 ,next[0] 沒有用到(也可以存放 next 數組的長度)。
    • 而串的存儲是從數組的下標 0 開始的,所以程序中為 str[i-1] 和 str[j-1]。
  • 初版(下面代碼只提供思路,具體代碼參考最后):

#include <stdio.h>
#include <string.h>

/**
 * @name   next_creat
 * @brief  簡版。時間復雜度O(n)
 * @param  
 * @retval 
 * @author
 */
void next_creat(char *str, int *next)
{
  int i = 1;
  next[1] = 0;
  int j = 0;
  while (i<strlen(str)) 
  {
    if (j == 0 || str[j-1] == str[i-1]) 
    {
      i++;
      j++;
      next[i] = j;
    }
    else
    {
      j = next[j]; // 指針j,遞歸回溯。 // 算法重點語句。
    }
  }
}
  • 優化版(下面代碼只提供思路,具體代碼參考最后):

    • 初版有個弊端,如模式串 caaaaabx

      • 用初版思維推導下,會發現當最后一個 a 匹配失敗時需要回溯,但是前面的最長公共前后綴都是-1,不就是和暴力算法一樣的效果嗎。
      • 另外既然最后一個字符 a 匹配不成功,那前面連續的 a 肯定匹配不成功的,所以就應該直接回溯到前面連續字符的前一個字符 c。參考下面代碼或者看大話數據結構 P142 頁。
    • 根據第 j 個的 next 值找它的 next 值 x 對應的第 x 個字符,並判斷第 j 個和第 x 個字符是否相等。

    • 若不相等,保持 val 值等於 next 值;若相等,val 值等於第 x 個值的 val 值。

#include <stdio.h>
#include <string.h>

/**
 * @name   next_creat
 * @brief  優化版。時間復雜度O(n)
 * @param  
 * @retval 
 * @author
 */
void nextval_creat(char *str, int *nextval)
{
  int i = 1;
  nextval[1] = 0;
  int j = 0;
  while (i<strlen(str)) 
  {
    if (j == 0 || str[j-1] == str[i-1]) 
    {
      i++;
      j++;
           if(str[i] != str[j])
               nextval[i] = j;/* 如果當前字符和前綴字符不同,則把子串的當前回溯指針j賦值給next */
           else
          nextval[i] = nextval[j]; /* 如果當前字符和前綴字符相同,則自己采用前綴字符的回溯值 */
    }
    else
    {
      j = nextval[j]; // 指針j,遞歸回溯。
    }
  }
}

基於 next 的 KMP 算法的實現

KMP 算法(下面代碼只提供思路,具體代碼參考最后):

/**
 * @name   kmp_index
 * @brief  
 * @param  
 * @retval 
 * @author
 */
int kmp_index(char *str_main, char *str_sub)
{
  int i = 1;
  int j = 1;
  int next[10];

  next_creat(str_sub, next);  //根據模式串T,初始化next數組

  while (i<=strlen(str_main) && j<=strlen(str_sub)) 
  {
    //j==0:代表模式串的第一個字符就和指針i指向的字符不相等;S[i-1]==T[j-1],如果對應位置字符相等,兩種情況下,指向當前測試的兩個指針下標i和j都向后移
    if (j==0 || str_main[i-1] == str_sub[j-1]) 
    {
      i++;
      j++;
    }
    else
    {
      j=next[j];//如果測試的兩個字符不相等,i不動,j變為當前測試字符串的next值
    }
  }
  if (j > strlen(str_sub)) 
  {
    //如果條件為真,說明匹配成功
    return i-(int)strlen(str_sub);
  }
  return -1;
}

KMP 時間復雜度

KMP 算法的時間復雜度:O(m+n)

  • get_next 的時間復雜度:O(m)
  • while 循環的時間復雜度:O(n)

參考代碼

串 & KPM 算法

/** @file         string_kmp.c
 *  @brief        采用kmp算法
 *  @details      詳細說明
 *  @author       lzm
 *  @date         2021-09-11 20:10:56
 *  @version      v1.0
 *  @copyright    Copyright By lizhuming, All Rights Reserved
 *  @blog         https://www.cnblogs.com/lizhuming/
 *
 **********************************************************
 *  @LOG 修改日志:
 **********************************************************
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define STRING_SIZE 100

/* status_e是函數的類型,其值是函數結果狀態代碼,如OK等 */
typedef enum {
    LZM_STRING_STATUS_ERR = -1,
    LZM_STRING_STATUS_OK,
    LZM_STRING_STATUS_FALSE,
}status_e;

typedef int elem_type;	/* elem_type類型根據實際情況而定,這里假設為int */

typedef char string_t[STRING_SIZE+1];

/**
 * @name   str_assing
 * @brief  生成一個其值等於 str_t 的串 str_s
 * @param  
 * @retval 
 * @author lzm
 */
status_e str_assing(string_t str_s, char *str_t)
{
    int i = 0;

	if(str_t == NULL || str_s == NULL || strlen(str_s) > STRING_SIZE)
		return LZM_STRING_STATUS_ERR;
	else
	{
		str_t[0] = strlen(str_s);

		for(i = 1; i <= str_t[0]; i++)
			str_t[i] = *(str_s + i-1);
  
		return LZM_STRING_STATUS_OK;
	}
}

/**
 * @name   str_copy
 * @brief  復制串 str_t 到串 str_s
 * @param  
 * @retval 
 * @author lzm
 */
status_e str_copy(string_t str_s, string_t str_t)
{ 
	int i = 0;

    if(str_s == NULL || str_t == NULL)
    {
        return LZM_STRING_STATUS_ERR;
    }

	for(i = 0; i <= str_t[0]; i++)
    {
		str_s[i] = str_t[i];
    }
  
	return LZM_STRING_STATUS_OK;
}

/**
 * @name   str_empty
 * @brief  判空
 * @param  
 * @retval 
 * @author lzm
 */
status_e str_empty(string_t str_t)
{ 
    if(str_t == NULL)
    {
        return LZM_STRING_STATUS_ERR;
    }

	if(str_t[0] == 0)
        return LZM_STRING_STATUS_OK;
  
	return LZM_STRING_STATUS_FALSE;
}

/**
 * @name   str_compare
 * @brief  
 * @param  
 * @retval str_s > str_t 則返回大於0,等就0, 小就負
 * @author lzm
 */
int str_compare(string_t str_s, string_t str_t)
{ 
	int i = 0;

    if(str_t == NULL || str_s == NULL)
    {
        return LZM_STRING_STATUS_ERR;
    }
  
    for(i = 1; i <= str_s[0] && i <= str_t[0]; ++i)
		if(str_s[i] != str_t[i])
			return str_s[i] - str_t[i];

	return str_s[0] - str_t[0];
}

/**
 * @name   str_length
 * @brief  獲取長度
 * @param  
 * @retval 
 * @author lzm
 */
int str_length(string_t str_s)
{ 
    if(str_s == NULL)
    {
        return LZM_STRING_STATUS_ERR;
    }

	return str_s[0];
}

/**
 * @name   str_clear
 * @brief  清空
 * @param  
 * @retval 
 * @author lzm
 */
int str_clear(string_t str_s)
{ 
    if(str_s == NULL)
        return LZM_STRING_STATUS_ERR;

    str_s[0] = 0;

	return LZM_STRING_STATUS_OK;
}

/**
 * @name   str_concat
 * @brief  拼接
 * @param  
 * @retval 
 * @author lzm
 */
int str_concat(string_t str_t, string_t str_s1, string_t str_s2)
{
    int i = 0;

    if(str_t == NULL || str_s1 == NULL || str_s2 == NULL)
        return LZM_STRING_STATUS_ERR;

    if(str_s1[0] + str_s2[0] <= STRING_SIZE)
	{ 
        /*  未截斷 */
		for(i=1; i<=str_s1[0]; i++)
			str_t[i] = str_s1[i];

		for(i=1; i <= str_s2[0]; i++)
			str_t[str_s1[0]+i]=str_s2[i];
  
		str_t[0] = str_s1[0] + str_s2[0];

		return LZM_STRING_STATUS_OK;
	}
	else if(str_s1[0] <= STRING_SIZE)
	{ 
        /*  截斷S2 */
		for(i=1; i <= str_s1[0]; i++)
			str_t[i] = str_s1[i];
  
		for(i=1;i<=STRING_SIZE-str_s1[0]; i++)
			str_t[str_s1[0]+i] = str_s2[i];
  
		str_t[0] = STRING_SIZE;

		return LZM_STRING_STATUS_FALSE;
	}
    else
    {
        return LZM_STRING_STATUS_ERR;
    }
}

/**
 * @name   str_insert
 * @brief  插串str_t入串str_s
 * @param  
 * @retval 
 * @author lzm
 */
int str_insert(string_t str_s, string_t str_t, int pos)
{
    int i = 0;

    if(str_t == NULL || str_s == NULL || pos < 1 || pos > str_t[0]+1)
    {
        return LZM_STRING_STATUS_ERR;
    }

	if(str_s[0] + str_t[0] <= STRING_SIZE)
	{ 
        /*  完全插入 */
		for(i=str_s[0]; i>=pos; i--)
			str_s[i+str_t[0]]=str_s[i]; // 先挪動str_s串騰出空間
  
		for(i=pos; i<pos+str_t[0]; i++)
			str_s[i] = str_t[i-pos+1];
  
		str_s[0] = str_s[0] + str_t[0];
  
		return LZM_STRING_STATUS_OK;
	}
	else
	{
        /*  部分插入 */
		for(i = STRING_SIZE; i<=pos; i--)
			str_s[i] = str_s[i-str_t[0]];
  
		for(i=pos; i<pos+str_t[0]; i++)
			str_s[i] = str_t[i-pos+1];
  
		str_s[0]=STRING_SIZE;

		return LZM_STRING_STATUS_FALSE;
	}
}

/**
 * @name   str_delete
 * @brief  刪除部分
 * @param  
 * @retval 
 * @author lzm
 */
int str_delete(string_t str_s, int pos, int len)
{
    int i = 0;

    if(str_s == NULL || pos < 1 || pos > str_s[0]-len+1)
        return LZM_STRING_STATUS_ERR;

    for(i=pos+len;i<=str_s[0];i++)
		str_s[i-len] = str_s[i];

    str_s[0]-=len;

	return LZM_STRING_STATUS_ERR;
}

/**
 * @name   next_creat
 * @brief  優化版。時間復雜度O(n)
 * @param  
 * @retval 
 * @author
 */
int nextval_creat(char *str, int *nextval)
{
    int i = 1;
    int j = 0;

    if(str == NULL || nextval == NULL)
        return LZM_STRING_STATUS_ERR;

    nextval[1] = 0;

    while (i<strlen(str)) 
    {
        if (j == 0 || str[j-1] == str[i-1]) 
        {
            i++;
            j++;
            if(str[i] != str[j])
                nextval[i] = j;/* 如果當前字符和前綴字符不同,則把子串的當前回溯指針j賦值給next */
            else
                nextval[i] = nextval[j]; /* 如果當前字符和前綴字符相同,則自己采用前綴字符的回溯值 */
        }
        else
        {
            j = nextval[j]; // 指針j,遞歸回溯。
        }
    }

    return LZM_STRING_STATUS_OK;
}

/**
 * @name   str_kmp_index
 * @brief  
 * @param  
 * @retval 
 * @author
 */
int str_kmp_index(string_t str_main, string_t str_sub, int pos)
{
    int i = 1;
    int j = 1;
    int *nextval = NULL;

    if(str_main == NULL || str_sub == NULL || pos < 1 || pos > str_main[0] + 1)
        return LZM_STRING_STATUS_ERR;

    nextval = (int *)malloc(str_sub[0]);
    if(nextval == NULL)
        return LZM_STRING_STATUS_ERR;

    nextval_creat(str_sub, nextval);  //根據模式串str_sub,初始化nextval數組

    while (i<=strlen(str_main) && j<=strlen(str_sub)) 
    {
        //j==0:代表模式串的第一個字符就和指針i指向的字符不相等;S[i-1]==T[j-1],如果對應位置字符相等,兩種情況下,指向當前測試的兩個指針下標i和j都向后移
        if (j==0 || str_main[i-1] == str_sub[j-1]) 
        {
            i++;
            j++;
        }
        else
        {
            j=nextval[j];//如果測試的兩個字符不相等,i不動,j變為當前測試字符串的next值
        }
    }
    if (j > strlen(str_sub)) 
    {
        //如果條件為真,說明匹配成功
        return i-(int)strlen(str_sub);
    }
    return LZM_STRING_STATUS_ERR;
}

/**
 * @name   str_replace
 * @brief  刪除部分
 * @param  
 * @retval 
 * @author lzm
 */
int str_replace(string_t str_s, string_t str_t, string_t str_v)
{
    int i = 0;

    if(str_s == NULL || str_t == NULL || str_empty(str_v) != LZM_STRING_STATUS_OK)
        return LZM_STRING_STATUS_ERR;

    do
	{
		i = str_kmp_index(str_s, str_t, i); /*  結果i為從上一個i之后找到的子串T的位置 */
		if(i) /*  串str_s中存在串str_t */
		{
			str_delete(str_s, i, str_length(str_t)); /*  刪除該串str_t */
			str_insert(str_s, str_v, i); /*  在原串str_t的位置插入串str_v */
			i+=str_length(str_v); /*  在插入的串str_v后面繼續查找串str_t */
		}
	}
    while(i);

	return LZM_STRING_STATUS_OK;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM