前端經常使用的查找和排序等必會手寫知識

有序數組插入數字

function insertNumber(arr, x) {
    
    //查找到第一個大於x的數字
    let b = newArr.find(e => e > x);
    
    
    if (b === undefined) { 
        // 若是b不存在,證實x是最大的數字,push到數組尾部
        newArr.push(x);
        
    } else {
    
        //獲取b的index,把新的數字插入到b的位置
        let bIndex = newArr.indexOf(b)
        newArr.splice(bIndex, 0, x)
    }
    
    return arr;

}

複製代碼

兩個有序數組合併成一個新的有序數組

  • 和插入同樣, 第一個解構,對第二個數組進行遍歷循環插入到第一個數組
function insert_some_numbers(arr1, arr2) {
    //建立新數組,並結構第一個數組
    var newArr = [...arr1];
    
    for (var i = 0; i < arr2.length; i++) {
        //獲取第二個數組中的每一位
        x = arr2[i];
        
        //在新解構的數組中查找第一個比他大的數組
        let b = newArr.find(e => e > x);
        
        //獲取到這個數字的索引並插入到他的前面
        let bindex = newArr.indexOf(b)
        newArr.splice(bindex === -1 ? newArr.length : bindex, 0, x)
    }
    return newArr;
}
let arr1 = [1, 2, 3, 4, 5, 6, 8, 9];
console.log(insert_some_numbers(arr1, [1, 2, 3]));
複製代碼

二分查找

  • 最大查找次數 Math.ceil(Math.log2(arr.length))
function binarySearch(arr, x) {
    var left = 0; //左邊界
    var right = arr.length - 1; //右邊界
    var guess;  // 遊標, 中間索引去小數點

    while (left <= right) { // 左側邊界小於右側邊界才執行
    
        guess = ((left + right) / 2) | 0;  //遊標索引, 中間索引去小數點
        
        if (arr[guess] === x) return guess // 當遊標索引就是查找的X的話,返回遊標索引
        
        else if (arr[guess] > x) right = guess - 1 // 遊標索引位 大於 x , 右邊界移動到guess以前。
        
        else left = guess + 1 // 遊標索引位 小於 x , 右邊界移動到guess以後。
    }
    
    return -1; //未查找到
}


var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(
    binarySearch(arr, 5) 
)
複製代碼

有序數組插入新數字

  • 模擬一個空位置循環比較,
  • 從數組尾部向前比較,前面的那一位比x大,比x大的那個數字索引+1,
  • 再跟前面的一位做比較,若是比X小, p+1位索引就被變量x 佔了
function insert_number_3(arr, x) {
    //p是下一個待比較元素的索引
    //p-1是空位
    //p不能小於0, 當P小於0表明, x是最小的
    let p = arr.length - 1;
    while (p >= 0 && arr[p] > x) {
        arr[p + 1] = arr[p];
        p--;
    }
    arr[p + 1] = x;
}
let arr1 = [1, 2, 3, 4, 5, 6, 8, 9];
insert_number_3(arr1, 6)
console.log(arr1)
複製代碼

插入排序

  • 性能最差
  • n^2/2 - n/2 次比較
  • 從數組的第1索引位置,逐次向前比較
  • 比較次數 1 + 2 + 3 + ··· + arr.length 次
function insertion_sort(arr) {
    //第一位不用去做比較,只有一位的數組能夠看做爲有序數組
    for (var i = 1; i < arr.length; i++) {
        insert(arr, i, arr[i]);
    }
}

function insert(arr, i, x) {
    let p = i - 1; // p 做爲被首先比較的元素
    // arr[i] 也就是 x  是待插入的元素
    while (p >= 0 && arr[p] > x) {
        // 當待插入的元素
        arr[p + 1] = arr[p];
        p--;
    }
    arr[p + 1] = x;
}
複製代碼

選擇排序

// 找到第一個最小的,放在第一位
// 再找到第二個最小的, 放在第二位
function select(array) {

    var len = array.length;
    
    //第零位到倒數第二位
    for (var i = 0; i < len - 1; i++) { 
        
        //先認定數組中的第i位是最小的, 再逐次跟後面的做比較
        var minnum = array[i];
        
        //第一位到最後一位
        for (var j = i + 1; j < len; j++) { 
            
            // 若是後面的更小的話, 作值的交換
            if (array[j] < minnum) {
                //值交換
                [array[j], minnum] = [minnum, array[j]]
            }
        }
        //比較事後, 給第i位賦值
        array[i] = minnum;
    }
}

var arr = [4, 5, 8, 9, 4, 2, 15, 6]
select(arr)
console.log(arr)
複製代碼

冒泡排序

  • 先找出最大的,再找出最小的
  • 性能最差
  • 排序次數: arr.length的等差數列求和次 -> n**n/2 -n/2 次
function bubble_sort(arr) {
    //值交換函數
    function swap(arr, x, j) {
        [arr[x],arr[j]] = [arr[j], arr[i]]
    }
    
    //先找出最大的,再找出最小的
    for (let i = arr.length - 1; i >= 1; i--) {
        //從1開始,每一次都和前一位做比較。
        for (let j = 1; j <= i; j++) {
            arr[j] < arr[j - 1] && swap(arr, j-1, j)
        }
    }
}

const arr2 = [91, 60, 96, 9, 30, 20, 31, 77, 81, 24];
bubble_sort(arr2)
console.log(arr2);
複製代碼

合併排序

//將數組分紅兩個數組, 
// 索引 [a,b) 和 [b,c)

//合併函數部分,
function merge(arr, a, b, c) {
    // 此時arr1 和 arr2 是 兩個有序數組
    let arr1 = arr.slice(a, b);
    let arr2 = arr.slice(b, c);

    //在兩個數組後面佈置哨兵
    arr1.push(Infinity);
    arr2.push(Infinity);
    //設置兩個數組的比較位置的索引的遊標索引
    // 若是這個數字被賦值,則索引+1
    var i = 0, j = 0;
    //循環給arr賦值
    for (let k = a; k < c; k++) {
        //k : 下一個寫入位置
        //i : arr1中的回寫位置
        //j :  arr2中的回寫位置
        arr[k] = arr1[i] < arr2[j] ? arr1[i++] : arr2[j++];
    }
}

function mergeSort(arr, a, c) {
    //判斷數組長度是否爲1
    if (c - a < 2) { return };
    const b = Math.ceil((a + c) / 2);
    //左側部分遞歸
    mergeSort(arr, a, b);
    //右側部分遞歸
    mergeSort(arr, b, c);
    //執行拼接
    merge(arr, a, b, c);
}

// 把數組拆成一個,一個的數組, 在執行拼接
複製代碼

雙路快速

function quickSort(arr) {
    //遞歸出口,數組長度爲0
    if (arr.length == 0) return [];
    // 創建比較數字, 左側數組, 右側數組
    var left = [];
    var right = [];
    var pivot = arr[0];
    //從數組第一位開始比較
    for (var i = 1; i < arr.length; i++) {
        if (arr[i] < pivot) {
            left.push(arr[i]);
        } else {
            right.push(arr[i]);
        }
    }
    // 遞歸 鏈接數組, 這個函數是純函數,結果不會改變原數組。
    return quickSort(left).concat(pivot, quickSort(right))
}
let arr = [4, 51, 59, 13, 1, 31, 3, 1, 8];
let result = quickSort(arr);
console.log(result)
複製代碼

三路快排

function qSort3(arr) {
    if (arr.length == 0) {
        return [];
    }
    //定義三個數組
    var left = [];
    //三路快排的核心是多了一箇中間數組, 中間數組放相等的值,避免了沒必要要的比較
    var center = []; 
    var right = [];

    //設置一個比較值
    var pivot = arr[0];

    //循環比較部分
    for (var i = 0; i < arr.length; i++) {
        if (arr[i] < pivot) {
            left.push(arr[i]);
        } else if (arr[i] == pivot) {
            center.push(arr[i]);
        } else {
            right.push(arr[i]);
        }
    }
    //循環遞歸 大於比較值的部分和小於比較值的部分
    return [...qSort3(left), ...center, ...qSort3(right)];
}
var newArr = qSort3([9, 4, 10, 8, 12, 2, 6, 7, 3, 1, 1, 0, 9, 1, 0])
console.log(newArr)
複製代碼

深度克隆

function deepClone2(origin, target) {
    var target = target || ((origin instanceof Array) ? [] : {});

    for (var prop in origin) {
        if (origin.hasOwnProperty(prop)) {
            if (typeof origin[prop] == 'object') {
                if (Object.prototype.toString.call(origin[prop]) === '[object Array]') {
                    target[prop] = [];
                } else {
                    target[prop] = {};
                }
                deepClone2(origin[prop], target[prop]);
            } else {
                target[prop] = origin[prop]
            }
        }
    }
    return target;
}
複製代碼

數組扁平化

function platArray(arr) {
    var newArr = [];
    function pushToArray(arr) {
        for (var i = 0; i < arr.length; i++) {
            if (arr[i] instanceof Array) {
                pushToArray(arr[i])
            } else {
                newArr.push(arr[i])
            }
        }
    }
    pushToArray(arr)
    return newArr;
}
console.log(platArray([[1, 2], [[[[3]]]], [{ name: "lyz", info: { age: "24" } }], [4, [5, [6]]]]));
// [1, 2, 3, { name: 'lyz', info: { age: '24' } }, 4, 5, 6]
複製代碼

字符串反轉

var str = 'abcde';

function strReverse(str) {
    // new String 出來的類數組是不能修改某一位的,
    // writeable是false;
    var strObj = [...new String(str)];
    var times = strObj.length / 2 | 0;
    for (var i = 0; i < times; i++) {
        var y = strObj.length - 1 - i;
        [strObj[i], strObj[y]] = [strObj[y], strObj[i]];

    }
    return strObj.join('');
}

console.log(strReverse(str));
複製代碼

觀察 者模式

//es6
 
        class Event {
            constructor() {
                this.cache = {};
            }

            on(type, handle) {
                if (!this.cache[type]) {
                    this.cache[type] = [handle];
                } else {
                    this.cache[type].push(handle)
                }
            }

            // 執行某一個type下面的所有函數
            emmit() {
                // argiments 第一位是type類型, 其他幾位是函數用的參數
                //獲取type
                var type = arguments[0];
                // 獲取參數, 
                var arg = [].slice.call(arguments, 1);
                // 依次執行數組裏面的函數
                for (var i = 0; i < this.cache[type].length; i++) {
                    this.cache[type][i].apply(this, arg)
                }
            }

            //清空某一個type數組
            empty(type) {
                this.cache[type] = [];
            }

            // 清空指定的type數組裏面的指定的hanle
            remove(type, handle) {
                var infos = this.cache[type];
                if (!infos) return false;
                if (!handle) {
                    infos && (infos.length = 0);
                } else {
                    for (var i = 0, len = infos.length; i < len; i++) {
                        if (infos[i] === handle) {
                            infos.splice(i, 1);
                        }
                    }
                }
            }
        }
複製代碼

簡易版的Promise (還不支持Promise內部的異步觸發)

//executor 函數是同步執行的函數 ,包含有兩個參數 resolve reject
// promise對象有三種狀態 pending, fulfilled 和 rejected

function myPromise(executor) {
    //在 es5中函數中執行函數的this指向問題
    var _this = this;

    // 設置初始狀態和resolve和reject函數初始參數
    _this.status = "pending";
    _this.resolveValue = null;
    _this.rejectValue = null;

    function resolve(value) {
        if (_this.status === 'pending') {
            _this.status = "fulfilled";
            _this.resolveValue = value;
        }
    }
    
    function reject(reason) {
        if (_this.status === 'pending') {
            _this.status = "rejected";
            _this.rejectValue = reason;
        }
    }
    
    //使用try catch來處理Promise中拋出異常的狀況
    try {
        executor(resolve, reject)
    } catch (e) {
        //若是拋出異常 , 執行reject就能夠了
        reject(e)
    }

}

//Promise原型鏈上的方法then的實現

myPromise.prototype.then = function (onFulfilled, onRejected) {
    var _this = this;
    
    if (_this.status === "fulfilled") {
        onFulfilled(_this.resolveValue)
    }
    if (_this.status === "rejected") {
        onRejected(_this.rejectValue)
    }
}
複製代碼

支持異步的簡易版Promise (不支持鏈式操做)

//executor 函數是同步執行的函數 ,包含有兩個參數 resolve reject
// promise對象有三種狀態
// pending, fulfilled 和 rejected

function myPromise(executor) {
    //在 es5中函數中執行函數的this指向問題
    var _this = this;

    // 設置初始狀態和resolve和reject函數初始參數
    _this.status = "pending";
    _this.resolveValue = null;
    _this.rejectValue = null;

    //執行數組, 裏面存入函數
    _this.ResolveCallbackList = [];
    _this.RejectCallbackList = [];

    function resolve(value) {
        if (_this.status === 'pending') {
            _this.status = "fulfilled";
            _this.resolveValue = value;
            //執行的時候已經能夠是異步了, 若是是異步的時候, 
            //數組裏面有函數,否則沒有函數
            _this.ResolveCallbackList.forEach(function (ele) {
                ele();
            })
        }
    }
    function reject(reason) {
        if (_this.status === 'pending') {
            _this.status = "rejected";
            _this.rejectValue = reason;
            //執行的時候已經能夠是異步了, 若是是異步的時候, 
            //數組裏面有函數,否則沒有函數
            _this.RejectCallbackList.forEach(function (ele) {
                ele();
            })
        }
    }
    //使用try catch來處理Promise中拋出異常的狀況
    try {
        executor(resolve, reject)
    } catch (e) {
        //若是拋出異常 , 執行reject就能夠了
        reject(e)
    }

}

//Promise原型鏈上的方法then的實現

myPromise.prototype.then = function (onFulfilled, onRejected) {
    var _this = this;
    if (_this.status === "fulfilled") {
        onFulfilled(_this.resolveValue)
    }
    if (_this.status === "rejected") {
        onRejected(_this.rejectValue)
    }
    
    //增長pending狀態的判斷, 若是是pending, 存入到相應的執行事件的數組當中。
    if (_this.status === "pending") {
        onFulfilled && _this.ResolveCallbackList.push(function () {
            onFulfilled(_this.resolveValue);
        });
        onRejected && _this.RejectCallbackList.push(function () {
            onRejected(_this.rejectValue);
        })
    }
}
複製代碼

簡易版Promise支持鏈式調用(then目前仍是同步的)

function myPromise(executor) {
    //在 es5中函數中執行函數的this指向問題
    var _this = this;

    // 設置初始狀態和resolve和reject函數初始參數
    _this.status = "pending";
    _this.resolveValue = null;
    _this.rejectValue = null;

    //執行數組, 裏面存入函數
    _this.ResolveCallbackList = [];
    _this.RejectCallbackList = [];

    function resolve(value) {
        if (_this.status === 'pending') {
            _this.status = "fulfilled";
            _this.resolveValue = value;
            _this.ResolveCallbackList.forEach(function (ele) {
                ele();
            })
        }
    }
    function reject(reason) {
        if (_this.status === 'pending') {
            _this.status = "rejected";
            _this.rejectValue = reason;
            _this.RejectCallbackList.forEach(function (ele) {
                ele();
            })
        }
    }
    //使用try catch來處理Promise中拋出異常的狀況
    try {
        executor(resolve, reject)
    } catch (e) {
        //若是拋出異常 , 執行reject就能夠了
        reject(e)
    }

}

//Promise原型鏈上的方法then的實現
//鏈式調用的核心:
//.then 執行後會返回一個 >>>新的Promise對象<<< 注意是新的

myPromise.prototype.then = function (onFulfilled, onRejected) {
    var _this = this;

    var nextPromise = new myPromise(function (resolve, reject) {
        if (_this.status === "fulfilled") {
            var nextResolveValue = onFulfilled(_this.resolveValue);
            //獲取then裏面的第一個函數的返回值,給下一個then的promise傳參
            //當 前一個Promise的同步觸發, 直接就執行下一個promise的resolve函數
            resolve(nextResolveValue);
        }
        if (_this.status === "rejected") {
            var nextRejectValue = onRejected(_this.rejectValue);
            //  這個也必需要是resolve的函數, 
            resolve(nextRejectValue);
        }

        if (_this.status === "pending") {
            onFulfilled && _this.ResolveCallbackList.push(function () {
                var nextResolveValue = onFulfilled(_this.resolveValue);
                //前一個Promise的異步觸發, 直接就執行下一個promise的resolve函數
                resolve(nextResolveValue);
            });
            onRejected && _this.RejectCallbackList.push(function () {
                var nextRejectValue = onRejected(_this.rejectValue);
                //  這個也必需要是resolve的函數
                resolve(nextRejectValue);
            })
        }
    });
    return nextPromise;
}
複製代碼

Promise實現空then()傳遞, .then()異步執行 .then()的拋錯處理

function myPromise(executor) {
    //在 es5中函數中執行函數的this指向問題
    var _this = this;

    // 設置初始狀態和resolve和reject函數初始參數
    _this.status = "pending";
    _this.resolveValue = null;
    _this.rejectValue = null;

    //執行數組, 裏面存入函數
    _this.ResolveCallbackList = [];
    _this.RejectCallbackList = [];

    function resolve(value) {
        if (_this.status === 'pending') {
            _this.status = "fulfilled";
            _this.resolveValue = value;
            _this.ResolveCallbackList.forEach(function (ele) {
                ele();
            })
        }
    }
    function reject(reason) {
        if (_this.status === 'pending') {
            _this.status = "rejected";
            _this.rejectValue = reason;
            _this.RejectCallbackList.forEach(function (ele) {
                ele();
            })
        }
    }
    //使用try catch來處理Promise中拋出異常的狀況
    try {
        executor(resolve, reject)
    } catch (e) {
        //若是拋出異常 , 執行reject就能夠了
        reject(e)
    }

}

//Promise原型鏈上的方法then的實現
//鏈式調用的核心:
//.then 執行後會返回一個 >>>新的Promise對象<<< 注意是**新的**
//
//空then原理 .then() 也就是 .then((val)=>val, err=>err)

myPromise.prototype.then = function (onFulfilled, onRejected) {
    var _this = this;

    //實現空then的數據的傳遞
    if (!onFulfilled) {
        onFulfilled = function (val) {
            return val;
        }
    }
    if (!onRejected) {
        onRejected = function (reason) {
            throw reason
        }
    }

    var nextPromise = new myPromise(function (resolve, reject) {
        if (_this.status === "fulfilled") {
            //使用then實現異步執行, 和拋出錯誤的處理
            setTimeout(function () {
                try {
                    var nextResolveValue = onFulfilled(_this.resolveValue);
                    //獲取then裏面的第一個函數的返回值,給下一個then的promise傳參
                    //當 前一個Promise的同步觸發, 直接就執行下一個promise的resolve函數
                    resolve(nextResolveValue);
                } catch (e) {
                    reject(e)
                }

            })
        }

        if (_this.status === "rejected") {
            setTimeout(function () {
                try {
                    var nextRejectValue = onRejected(_this.rejectValue);
                    //  這個也必需要是resolve的函數, 
                    resolve(nextRejectValue);
                } catch (e) {
                    reject(e)
                }
            })
        }

        if (_this.status === "pending") {
            onFulfilled && _this.ResolveCallbackList.push(function () {
                setTimeout(function () {
                    try {
                        var nextResolveValue = onFulfilled(_this.resolveValue);
                        //前一個Promise的異步觸發, 直接就執行下一個promise的resolve函數
                        resolve(nextResolveValue);
                    } catch (e) {
                        reject(e)
                    }
                })
            });
            onRejected && _this.RejectCallbackList.push(function () {
                setTimeout(function () {
                    try {
                        var nextRejectValue = onRejected(_this.rejectValue);
                        //  這個也必需要是resolve的函數
                        resolve(nextRejectValue);
                    } catch (e) {
                        reject(e)
                    }
                })
            })
        }
    });
    return nextPromise;
}

複製代碼

完整的Promise函數實現原理

function MyPromise(executor) {
    //在 es5中函數中執行函數的this指向問題
    var _this = this;

    // 設置初始狀態和resolve和reject函數初始參數
    _this.status = "pending";
    _this.resolveValue = null;
    _this.rejectValue = null;

    //執行數組, 裏面存入函數
    _this.ResolveCallbackList = [];
    _this.RejectCallbackList = [];

    function resolve(value) {
        if (_this.status === 'pending') {
            _this.status = "fulfilled";
            _this.resolveValue = value;
            _this.ResolveCallbackList.forEach(function (ele) {
                ele();
            })
        }
    }
    function reject(reason) {
        if (_this.status === 'pending') {
            _this.status = "rejected";
            _this.rejectValue = reason;
            _this.RejectCallbackList.forEach(function (ele) {
                ele();
            })
        }
    }
    //使用try catch來處理Promise中拋出異常的狀況
    try {
        executor(resolve, reject)
    } catch (e) {
        //若是拋出異常 , 執行reject就能夠了
        reject(e)
    }

}

//Promise原型鏈上的方法then的實現
//鏈式調用的核心:
//.then 執行後會返回一個 >>>新的Promise對象<<< 注意是**新的**
//
//空then原理 .then() 也就是 .then((val)=>val, err=>err)

//這個函數用來解決then裏面的函數返回一個新的new promise的
function ResolutionReturnValue(returnValue, resolve, reject) {
    if (returnValue instanceof MyPromise) {
        //若是是promise對象, 用then處理返給下一個then,
        returnValue.then(function (val) {
            resolve(val);
        }, function (reason) {
            reject(reason);
        })
    } else {
        // returnValue 是一個普通的值
        resolve(returnValue);

    }
}



MyPromise.prototype.then = function (onFulfilled, onRejected) {
    var _this = this;

    //實現空then的數據的傳遞
    if (!onFulfilled) {
        onFulfilled = function (val) {
            return val;
        }
    }
    if (!onRejected) {
        onRejected = function (reason) {
            throw reason
        }
    }

    var nextPromise = new MyPromise(function (resolve, reject) {
        if (_this.status === "fulfilled") {
            //使用then實現異步執行, 和拋出錯誤的處理
            setTimeout(function () {
                try {
                    //nextResolveValue 他有多是一個對象new Promise(), 後續再書中進行判斷
                    var nextResolveValue = onFulfilled(_this.resolveValue);
                    //由於是異步的代碼, nextPromise是能夠被傳到函數執行中的, 由於setTimeout發身在給nextPromise的賦值以後
                    ResolutionReturnValue(nextResolveValue, resolve, reject);
                } catch (e) {
                    reject(e)
                }

            })
        }

        if (_this.status === "rejected") {
            setTimeout(function () {
                try {
                    var nextRejectValue = onRejected(_this.rejectValue);
                    //  這個也必需要是resolve的函數, 
                    ResolutionReturnValue(nextRejectValue, resolve, reject);
                } catch (e) {
                    reject(e)
                }
            })
        }

        if (_this.status === "pending") {
            onFulfilled && _this.ResolveCallbackList.push(function () {
                setTimeout(function () {
                    try {
                        var nextResolveValue = onFulfilled(_this.resolveValue);
                        //前一個Promise的異步觸發, 直接就執行下一個promise的resolve函數
                        ResolutionReturnValue(nextResolveValue, resolve, reject);
                    } catch (e) {
                        reject(e)
                    }
                })
            });
            onRejected && _this.RejectCallbackList.push(function () {
                setTimeout(function () {
                    try {
                        var nextRejectValue = onRejected(_this.rejectValue);
                        //  這個也必需要是resolve的函數
                        ResolutionReturnValue(nextRejectValue, resolve, reject);
                    } catch (e) {
                        reject(e)
                    }
                })
            })
        }
    });

    return nextPromise;
}

//模塊導出 先後端均可以用
try {
    module.exports = MyPromise
} catch (e) { }
複製代碼

Promise.all

MyPromise.all = function(promises) {
    return new Promise(function (resolve, reject) {
        //類型判斷
        if (!isArray(promises)) {
            return reject(new TypeError('arguments must be an array'));
        }
        //目前已經resolve的數量
        var resolvedCounter = 0;
        //獲取參數數組長度
        var promisesNum = promises.length;
        // 所有成功是的返回值是一個數組,數組裏面是每個成功的promise的resolve的參數
        var resolvedValues = new Array(promiseNum);
        //對數組進行遍歷,由於是異步,因此要防止產生閉包。
        for (var i = 0; i < promiseNum; i++) {
            (function (i) {
                Promise.resolve(promises[i]).then(function (value) {
                    resolvedCounter++
                    resolvedValues[i] = value
                    // 只有resolve的數量和promise的數量一致的時候纔會觸發最外層的resolve執行
                    if (resolvedCounter == promisesNum) {
                        return resolve(resolvedValues)
                    }
                }, function (reason) {
                    return reject(reason)
                })
            })(i)
        }
    })
}


複製代碼

本菜鳥忘了的時候複習用的。

相關文章
相關標籤/搜索