1. 程式人生 > >js資料結構和演算法(一)陣列和散列表

js資料結構和演算法(一)陣列和散列表

程式設計=資料結構+演算法

一.資料結構

1.什麼是資料結構

資料結構就是關係,沒錯,就是資料元素相互之間存在的一種或多種特定關係的集合

傳統上,我們把資料結構分為邏輯結構和物理結構。
邏輯結構:是指資料物件中資料元素之間的相互關係,也是我們今後最需要關注和討論的問題。
物理結構:是指資料的邏輯結構在計算機中的儲存形式。

2.常用的資料結構有:
陣列,佇列(queue),堆(heap),棧(stack),連結串列(linked list ),樹(tree),圖(graph)和散列表(hash)


棧(stack):運算只在表的一端進行;佇列(Queue):運算只在表的兩端進行。
佇列(queue)是隻允許在一端進行插入操作,而在另一端進行刪除操作的線性表。
與棧相反,佇列是一種先進先出(First In First Out, FIFO)的線性表。

與棧相同的是,佇列也是一種重要的線性結構,實現一個佇列同樣需要順序表或連結串列作為基礎。

二.資料結構分析

(一).陣列

陣列的概念:陣列是一個構造型別的資料結構。陣列是許多個相同型別的資料的集合。


陣列分類:一維陣列,二維陣列【行 列】,多維陣列(三維以上【三維陣列  行 列 層】)

1.字串分割為陣列split與陣列元素拼接轉字串join

var sentence = "I love China";
/**1.字串.split(分隔符) 將字串生成為陣列*/
var words = sentence.split(" ");
var arr=[];
for (var i = 0; i < words.length; ++i) {
	arr.push(words[i])
}
console.log(arr)// ["I", "love", "China"]

//2.陣列轉字串   
/*.join(分隔符) 陣列各元素間放分隔符並連線成一個字串
 * join("") 就是 直接將陣列個元素拼接起來生字串
 * .toString()連線成字串後 預設中間會用,隔開
 */
var object1=arr.join(" ");
var object2=arr.toString();
console.log(object1)//I love China
console.log(object2)//I,love,China

2.indexOf-查詢陣列是否存在某元素及下標

var names = ["David","Cynthia","Raymond","Clayton","Jennifer"];
var fondName ='Clayton';
/**1.
 * 陣列.indexOf(引數值) 引數值是否存在於陣列,
 * 存,返第一個出現該元素的下標;不存,返-1;
 *
 * 陣列.lastIndexOf(引數值)
 * 反序找第一個的下標(如果出現,否則返-1)
 *
 * */
var position = names.indexOf(fondName);
if (position >= 0) {
    console.log("找到" + fondName + "在" + position+'位置');//找到Clayton在3位置
}else {
   console.log(fondName + "不選中陣列中");
}

3.陣列中間新增和刪除修改元素splice

/**
 * 1.splice() 將現有陣列進行擷取,返回所擷取生成出來的陣列,且現有陣列改變,是擷取後的陣列
 * 可用於為一個數組增加或移除或修改元素
 * 引數一:擷取(刪除)的起始索引(0是第一個元素)
 * 引數二:擷取(刪除)的元素的個數
 * 引數三:刪除擷取後要新增進陣列的元素(可以是個陣列)
 * */

/**2.
 * 陣列中間插入元素(放在數組裡插入)
 * */
var nums = [1,2,3,7,8,9];
var newElements = [4,5,6];
nums.splice(3,0,newElements);
console.log(nums); //[1, 2, 3, Array(3), 7, 8, 9]

/**3.
 * 要插入陣列的元素不必組織成一個數組, 它可以是任意的元素序列
 * */
var nums = [1,2,3,7,8,9];
nums.splice(3,0,4,5,6);
console.log(nums);// 1,2,3,4,5,6,7,8,9

/**4.
 * 從陣列中刪除元素
 * */
var nums = [1,2,3,100,200,300,400,4,5];
nums.splice(3,4);
console.log(nums); // 1,2,3,4,5

4.不生成新陣列的迭代器方法

forEach每個元素都操作--every所有都滿足--some有一個滿足--reduce累計操作

/**
 * 1. 陣列.forEach(func) 對陣列每個元素執行某操作
 * 它接受一個函式作為引數,對陣列中的每個元素使用該函式
 * */
function squareFunc(num) {
    console.log(num, num * num); //列印多個字元的時候自動中間會加空格
}
var nums = [1, 2, 3];
nums.forEach(squareFunc);// 1 1   2 4  3  9

/**
 * 2. 陣列.every(func), 檢查陣列中每個元素是否滿足某條件
 * 它接受一個返回值為布林型別的函式, 對陣列中的每個元素使用該函式。
 * 如果對於所有的元素,該函式均返回 true, 則該方法返回 true
 *
 * 陣列.some(func)是否存在一個元素滿足
 * 也接受一個返回值為布林型別的函式, 只要有一個元素使得該函式返回 true,
 * 該方法就返回 true
 * */
function isEven(num) {
    return num % 2 == 0;
}
var nums = [1, 3, 5, 8, 11];
var even = nums.some(isEven);
if (even==true) {
    console.log("all numbers are even");
} else {
    console.log("not all numbers are even");
}

/**
 * 3.
 * reduce() 陣列中的各個元素累計進行操作
 * 它接受一個函式, 返回一個值。 該方法會從一個累加值開始, 不斷對累加值和
 * 陣列中的後續元素呼叫該函式, 直到陣列中的最後一個元素, 最後返回得到的累加值。
 * */

//使用 reduce() 方法為陣列中的元素求和:
function add(runningTotal, currentValue) {
    return runningTotal + currentValue;
}
var nums = [1,2,3,4];
var sum = nums.reduce(add); //接受函式
console.log(sum); // 顯示10

//reduce() 方法也可以用來將陣列中的元素連線成一個長的字串
function concat(accumulatedString, item) {
    return accumulatedString + item;
}
var words = ["the ", "quick ","brown ", "fox "];
var sentence = words.reduce(concat);
console.log(sentence); // 顯示 "the quick brown fox"


/**
 * 4.reduceRight() 方法,從右到左執行。
 * 下面的程式使用 reduceRight() 方法將陣列中的元素進行翻轉:
 * */
function concat(accumulatedString, item) {
    return accumulatedString + item;
}
var words = ["the ", "quick ","brown ", "fox "];
var sentence = words.reduceRight(concat);
console.log(sentence); // 顯示 "fox brown quick the"

5.生成新陣列的迭代器方法

map每個元素都執行某操作結果組成的陣列-filter陣列中滿足某條件的元素組成的陣列

/**
 * 1. 陣列.map(func)
 * map() 和 forEach() 有點兒像,
 * 對陣列中的每個元素使用某個函式。 兩者的區別是
 * map() 返回一個新的陣列, 該陣列的元素是對原有元素應用某個函式得到的結果
 * */
function curve(grade) {
    return grade += 5;
}
var grades = [77, 65, 81, 92, 83];
var newgrades = grades.map(curve);
console.log(newgrades); // [82, 70, 86, 97, 88]

/**
 * 2.下面是對一個字串陣列使用 map() 方法的例子:
 * 陣列 acronym 儲存了陣列 words 中每個元素的第一個字母。
 * 然而, 如果想將陣列顯示為真正的縮略形式, 必須想辦法除掉連線每個陣列元素的逗號,
 * 如果直接呼叫 toString() 方法, 就會顯示出這個逗號。
 * 使用 join() 方法, 為其傳入一個空字串作為引數, 則可以幫助我們解決這個問題
 * */
function first(word) {
    return word[0];
}
var words = ["for", "your", "information"];
var acronym = words.map(first);
console.log(acronym)//["f", "y", "i"]
console.log(acronym.join("")); // 顯示 "fyi"

/**
 * 3.filter() 傳入一個返回值為布林型別的函式。
 * 和 every() 方法不同的是,
 * 當對陣列中的所有元素應用該函式,該方法並不返回 true,
 * 而是返回一個新陣列, 該陣列包含應用該函式後結果為 true 的元素。
 * */
//下列程式篩選陣列中的奇數和偶數元素
function isEven(num) {
    return num % 2 == 0;
}

function isOdd(num) {
    return num % 2 != 0;
}
var nums = [];
for (var i = 0; i < 20; ++i) {
    nums[i] = i + 1;
}
var evens = nums.filter(isEven);
console.log("Even numbers: ");
console.log(evens);//[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
var odds = nums.filter(isOdd);
console.log("Odd numbers: ");
console.log(odds);//[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

//下面使用 filter() 篩選所有成績及格的分數:
function passing(num) {
    return num >= 60;
}
var grades = [];
for (var i = 0; i < 10; ++i) {
    grades[i] = Math.floor(Math.random() * 101);
}
var passGrades = grades.filter(passing);
console.log("All grades:");
console.log(grades);// [74, 86, 34, 49, 5, 5, 21, 28, 27, 47]
console.log("Passing grades: ");
console.log(passGrades);//[74, 86]

//還可以使用 filter() 方法過濾字串陣列,下面這個例子過濾掉了那些不包含“ cie” 的單詞:
function afterc(str) {
    if (str.indexOf("cie") > -1) {
        return true;
    }
    return false;
}
var words = ["recieve","deceive","percieve","deceit","concieve"];
var misspelled = words.filter(afterc);
console.log(misspelled); // 顯示 ["recieve", "percieve", "concieve"]

6.二維陣列和多維陣列

//直接初始化
 var arr=[[11,12,13],[21,22,23],[31,32,33]];
 console.log(arr[0][0])//11
/**
 * 2.建立二維陣列
 * 比較好的方式是遵照 JavaScript: TheGood Parts( O’Reilly) 一書第 64 頁的例子,
 * 通過擴充套件 JavaScript 陣列物件, 為其增加了一個新方法,
 * 該方法根據傳入的引數, 設定了陣列的行數、 列數和初始值
 * */
Array.matrix = function(numrows, numcols, initial) {
    var arr = [];
    for (var i = 0; i < numrows; ++i) {
        var columns = [];
        for (var j = 0; j < numcols; ++j) {
            columns[j] = initial;
        }
        arr[i] = columns;
    }
    return arr;
}

//測試該生成二維陣列方法的一些測試程式碼
var nums = Array.matrix(3,3,2);
console.log(nums); // [ [2, 2, 2],[2, 2, 2], [2, 2, 2]]
nums[1][2]=4;
console.log(nums); // [ [2, 2, 2],[2, 2, 4], [2, 2, 2]] /把2改成4

/**
 * 3.處理二維陣列的元素
 * 兩種最基本的方式: 按行x訪問和按列y訪問
 * */

/**
 * 按行x訪問:
 * 外層迴圈對應行,內層迴圈對應列,每次對每一行的元素進行一些操作
 *
 * 以陣列 grades 為例, 每一行對應一個學生的成績記錄。
 * 可以將該學生的所有成績相加, 然後除以科目數得到該學生的平均成績。
 * (89+77+78)/3=81.33
 * toFixed()四省五入 保留幾個小數點
 * */
var grades = [[89, 77, 78],[76, 82, 81],[91, 94, 89]];
var total = 0;
var average = 0.0;
for (var x = 0; x < grades.length; x++) {
    for (var y = 0; y < grades[x].length; y++) {
        total += grades[x][y];
    }
    average = total / grades[x].length;
    console.log("Student " + parseInt(x+1) + " average: " +average.toFixed(2));
    total = 0;
    average = 0.0;
}

/**
 * 按列訪問:
 * 外層迴圈對應列,內層迴圈...,每次對每一列的元素進行一些操作
 *
 * 下面的程式計算了一個學生各科的平均成績,即:每一列的資料想加取平均值:
 * 如(89+76+91)/3=885.33
 * */
var grades2 = [[89, 77, 78],[76, 82, 81],[91, 94, 89]];
var total2 = 0;
var average2 = 0.0;
for (var y = 0; y < grades2.length; y++) {
    for (var x = 0; x < grades2[y].length; x++) {
        total2 += grades2[x][y];
    } 
    average2 = total2 / grades2[y].length;
    console.log("Test " + parseInt(y+1) + " average2: " +average2.toFixed(2));
    total2 = 0;
    average2 = 0.0;
}

/**
 * 4.參差不齊的陣列
 * 參差不齊的陣列是指陣列中每行的元素個數彼此不同。 有一行可能包含三個元素, 另一行
 * 可能包含五個元素, 有些行甚至只包含一個元素。 很多程式語言在處理這種參差不齊的數
 * 組時表現都不是很好, 但是 JavaScript 卻表現良好, 因為每一行的長度是可以通過計算得到的
 * */
//假設陣列 grades 中, 每個學生成績記錄的個數是不一樣的, 不用修改程式碼, 依然可以正確計算出正確的平均分:
var grades3 = [[89, 77],[76, 82, 81],[91, 94, 89, 99]];
var total3 = 0;
var average3 = 0.0;
for (var x = 0; x < grades3.length; x++) {
    for (var y = 0; y < grades3[x].length; y++) {
        total3 += grades3[x][y];
    }
    average3 = total3 / grades3[x].length;
    console.log("Student3 " + parseInt(x+1) + " average3: " + average3.toFixed(2));
    total3 = 0;
    average3 = 0.0;
}

/**
 * 5.物件陣列
 * 物件組成的陣列,陣列的方法和屬性對物件依然適用。
 * */

/**
 * 注意 這裡通過一個函式生成了一個物件    
 * 生成物件的函式裡傳入引數,然後設定   this.屬性 = ...  this.方法 = function...
 * 這樣的函式即建構函式
 * */
function Point(x,y) {
    this.x = x;
    this.y = y;
}
function displayPts(arr) {
    for (var i = 0; i < arr.length; ++i) {
        console.log(arr[i].x + ", " + arr[i].y);
    }
}

/**
 * 注意 這裡通過 var ... = new 建構函式(實際引數)
 * 生成了該物件的一個例項物件
 * */
var p1 = new Point(1,2);
var p2 = new Point(3,5);
var p3 = new Point(2,8);
var p4 = new Point(4,4);
//物件組成的陣列
var points = [p1,p2,p3,p4];
for (var i = 0; i < points.length; ++i) {
    console.log("Point " + parseInt(i+1) + ": " + points[i].x + ", " + points[i].y);
}
var p5 = new Point(12,-3);
//使用 push() 方法將點 (12, -3) 新增進陣列, 使用 shift() 方法將點 (1, 2) 從陣列中移除。
points.push(p5);
console.log("After push: ");
displayPts(points);
points.shift();
console.log("After shift: ");
displayPts(points);

/**
 * 6.物件中的陣列
 * 在物件中, 可以使用陣列儲存複雜的資料。
 * 實際演算法應用與解決方案中,很多資料都被實現成一個物件,物件內部使用陣列儲存資料。
 *
 * 下例中, 建立了一個物件, 用於儲存觀測到的周最高氣溫。
 * 該物件有兩個方法, 一個方法用來增加一條新的氣溫記錄,
 * 另外一個方法用來計算儲存在物件中的平均氣溫
 *
 * 很實用和常用的技巧!!!
 * */
//物件建構函式
function WeekTemps() {
    this.dataStore = []; //物件建構函式裡 設定某些屬性為一個數組儲存比較複雜的資料
    this.add = add; //設定物件的方法
    this.average = average;
}

//定義物件方法的操作,裡面使用this.屬性名 代表物件的某屬性
function add(temp) {
    this.dataStore.push(temp);  //對物件的陣列型資料進行陣列式操作
}
function average() {
	console.log(this.dataStore)//[52, 55, 61, 65, 55, 50, 52, 49]
    var total = 0;
    for (var i = 0; i < this.dataStore.length; ++i) {
        total += this.dataStore[i];
    }
    return total / this.dataStore.length;
}
var thisWeek = new WeekTemps();
thisWeek.add(52);
thisWeek.add(55);
thisWeek.add(61);
thisWeek.add(65);
thisWeek.add(55);
thisWeek.add(50);
thisWeek.add(52);
thisWeek.add(49);
console.log(thisWeek.average()); // 54.875

(二).散列表

1.列表概念

     在日常生活中,人們經常要使用列表,比如我們有時候要去購物時,為了購物時東西要買全,我們可以在去之前,列下要買的東西,這就要用的列表了,或者我們小時候上學那段時間,每次考完試後,學校都會列出這次考試成績前十名的同學的排名及成績單,等等這些都是列表的列子。我們計算機內也在使用列表,那麼列表適合使用在什麼地方呢?不適合使用在什麼地方呢?

適合使用在:當列表的元素不是很多的情況下,可以使用列表,因為對列表中的元素查詢或者排序時,效率還算非常高,反之:如果列表元素非常多的情況下,就不適合使用列表了。如果儲存的順序不重要(順序重要的話可以考慮如堆疊等), 也不必對資料進行查詢, 那麼列表就是一種再好不過的資料結構。 對於其他一些應用, 列表就顯得太過簡陋了

總結:列表是一組有序的資料。每個列表中的資料項稱為元素。在javascript中,列表中的元素可以是任意資料型別。列表中可以儲存多少元素並沒有事先約定。但是實際使用時元素數量受到程式記憶體的限制

2.屬性


3.使用

/**
 * 1.實現列表類,定義建構函式
 * 注意這裡定義的刪除查詢等方法都是傳入一整個元素的值,列表由一系列元素組成,元素即最小的那個單元
 * */
function List() {
    this.listSize = 0; //listSize是屬性  列表的元素個數
    this.pos = 0;// 列表的當前位置 是第幾個
    this.dataStore = []; // 初始化一個空陣列來儲存列表元素,即底層資料結構是陣列  
}
List.prototype = {
    // 給列表末尾新增元素  變數 listSize 加 1
    append: function(element) {
        var self = this;
        self.dataStore[this.listSize++] = element;
    },

    /*remove()從列表中刪除元素
    * 需要在列表中找到該元素, 然後刪除它, 並且調整底層的陣列物件以填補刪除該元素後留下的空白。
 	* js中可以使用 splice() 方法簡化這一過程。
 	* 
 	*  remove() 方法使用 find() 方法返回的位置對陣列 dataStore 進行擷取。 陣列改變後, 將變
	* 量 listSize 的值減 1, 以反映列表的最新長度。 如果元素刪除成功, 該方法返回 true,
	* 否則返回 false。
    */
    remove: function(element) {
        var self = this;
        var curIndex = self.find(element);
        if(curIndex > -1) {
            self.dataStore.splice(curIndex,1);
            --self.listSize;
            return true;
        }
        return false;
    },

    /*find() 方法通過對陣列物件 dataStore 進行迭代,查詢給定的元素。 
     * 查詢列表中的元素 返回索引
     * 如果找到,就返回該元素在列表中的位置,否則返回 -1,
     */
    find: function(element) {
        var self = this;
        for(var i = 0,dataLen = self.dataStore.length; i < dataLen; i++) {
            if(self.dataStore[i] == element) {
                return i;
            }
        }
        return -1;
    },
    
    // 返回列表中元素的個數
    length: function() {
        return this.listSize;
    },

    /*顯示列表中的元素
     * 該方法返回的是一個數組, 而不是一個字串, 但它的目的是為了顯示列表的
 	* 當前狀態, 因此返回一個數組就足夠了。
     */
    toString: function(){
        return this.dataStore;
    },

    /*insert() 在指定元素後面插入一個元素
     *  insert() 方法用到了 find() 方法, find() 方法會尋找傳入的 after 引數在列
	 * 表中的位置, 找到該位置後, 使用 splice() 方法將新元素插入該位置之後, 然後將變數
	 * listSize 加 1 並返回 true, 表明插入成功。
	 * 
     * @param element 當前的元素
     * @param elementAfter 把當前的元素插入到此元素後面
     */
    insert: function(element,elementAfter){
        var self = this;
        var insertPos = self.find(elementAfter);
        if(insertPos > -1) {
            self.dataStore.splice(insertPos+1,0,element);
            ++self.listSize;
            return true;
        }
        return false;
    },
    
    /* 清空列表中的所有元素
    * clear() 方法使用 delete 操作符刪除陣列 dataStore, 接著在下一行建立一個空陣列。 最
 	* 後一行將 listSize 和 pos 的值設為 1, 表明這是一個新的空列表
    */
    clear: function() {
        delete this.dataStore;
        this.dataStore = [];
        this.listSize = this.pos = 0;
    },
    
    // 判斷給定的元素是否在列表中
    contains: function(element) {
        var self = this;
        for(var i = 0,ilen = self.dataStore.length; i < ilen; i++) {
            if(self.dataStore[i] == element) {
                return true;
            }
        }
        return false;
    },
  
  /**
 * 下面的方法都是通過控制當前位置 pos 和 listSize 來實現的
 * */

    // 將列表中的當前元素移動到第一個位置
    front: function(){
        this.pos = 0;
    },
    // 將列表中當前的元素移動到最後一個位置
    end: function(){
        this.pos = this.listSize - 1;
    },
    // 將當前位置 後移一位
    prev: function(){
        if(this.pos > 0) {
            --this.pos;
        }
    },
    // 將當前位置 前移一位
    next: function(){
        if(this.pos < this.listSize - 1) {
            ++this.pos;
        }
    },
    // 返回列表的當前位置
    curPos: function(){
        return this.pos;
    },
    // 當前位置移動移動到某個位置(傳入的是位置數字,從零開始)
    moveTo: function(n) {
        this.pos = n;
    },
    // 返回當前位置的元素
    getElement:function(){
        return this.dataStore[this.pos];
    }
};

//  下面來執行上面的方法
//建立一個列表例項物件
var names = new List();
names.append("Clayton");
names.append("Raymond");
names.append("Cynthia");
names.append("Jennifer");
names.append("Bryan");
names.append("Danny");
/*console.log(names) 輸出
 * dataStore:(6) ["Clayton", "Raymond", "Cynthia", "Jennifer", "Bryan", "Danny"]
 * listSize:6    pos:0
 */

//1.現在移動到列表中的第一個元素並且顯示它:
names.front();
console.log(names.getElement()); // 顯示 Clayton

//2.接下來向後移動一個單位並且顯示它:
names.next();
console.log(names.getElement()); // 顯示 Raymond


//3.先向前移動兩次, 然後向後移動一次, 顯示出當前元素, 看看 prev() 方法的應用
names.next();
names.next();
names.prev();
console.log(names.getElement()); // 如果2執行的話,顯示 Cynthia 如果不執行2,則顯示Raymond

/**
 * !遍歷!
 * 由前向後遍歷列表:
 * 在 for 迴圈的一開始, 將列表的當前位置設定為第一個元素。 只要 curPos 的值小於列表
 * 的長度-1 (因為pos是從0開始的,比較完之後才會移動next() ), 就一直迴圈, 每一次迴圈都呼叫 next() 方法將當前位置向前移動一位。
 * */
//這裡用names.pos++比較好,因為next() pos永遠到不了names.length(),會一直迴圈
for (names.front(); names.curPos() < names.length(); names.pos++) {
    console.log(names.getElement());
    //console.log(names.currPos());
}
//但注意經過上面的遍歷操作,pos指向的是最後一位+1,所以要 -1一次
names.pos -= 1;

/**
 * 從後向前遍歷列表
 * 迴圈從列表的最後一個元素開始, 噹噹前位置大於或等於 0 時, 呼叫 prev() 方法後移一位。
 *
 * 迭代器只是用來在列表上隨意移動, 而不應該和任何為列表增加或刪除元素的方法一起使用
 * */
//這裡用names.pos--比較好,因為pre() pos永遠到0就不會降了,會一直迴圈
for(names.end(); names.curPos() >= 0; names.pos--) {
    console.log(names.getElement());
}
//但注意經過上面的遍歷操作,pos指向的是-1,所以要 +1一次
names.pos += 1;

參考: