js資料結構和演算法(一)陣列和散列表
阿新 • • 發佈:2019-02-06
程式設計=資料結構+演算法
一.資料結構
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;
參考: