圖說演算法使用JavaScript(九)

5-3徹底完轉單向串列演算法

在JavaScript語言中,如果以動態配置產生鏈結串列的節點,必須先行自訂一個類別,接著在該類別中定義一個指標欄位,用意在指向下一個鏈結點,及至少一個資料欄位。例如我們宣告一學生成績串列節點的結構宣告,並且包含下面兩個資料欄位;姓名name、成績score,與一個指標欄位next。可以宣告如下:
class student{
constructor(){
this.name='';
this.score=0;
this.next=null;
}
}
當各位完成節點類別的宣告後,就可以動態建立鏈結串列中的每個節點。假設我們現在要新增一個節點至串列尾端,且ptr指向串列的第一個節點,在程式上必須設計四個步驟:
1.動態配置記憶體空間給新節點使用。
2.將原列尾端的指標欄next指向新元素所在的記憶體位置。
3.將ptr指標指向新節點的記憶體位置,表示這是新的串列尾端。
4.由於新節點目前為串列最後一個元素,所以將它的指標欄next指向null。
例如要將s1的next變數指向s2,而且s2的next變數指向null:
s1.next = s2;
s2.next = null;
由於串列的基本特性就是next變數將會指向下一個節點,這時s1節點與s2節點間的關係就如下圖所示:
5-3-1單向鏈結串列的連結

JS           concatlist.js

/[示範]:單向串列的連結功能
var concatlist=(ptr1,ptr2)=>{
	ptr=ptr1;
	while (ptr.next!=null) ptr=ptr.next;
	ptr.next=ptr2;
	return ptr1;
}

class employee{
	constructor()  {
		this.num=0;
		this.salary=0;
		this.name='';
		this.next=null;
	}
}

findword=0;
data=[];
namedata1=['Allen','Scott','Marry','Jon',
			'Mark','Ricky','Lisa','Jasica',
		   'Hanson','Amy','Bob','Jack'];
namedata2=['May','John','Michal','Andy',
			'Tom','Jane','Yoko','Axel',
		   'Alex','Judy','Kelly','Lucy'];
for (i=0; i<12;i++){
	data[i]=[];
	data[i][0]=i+1;
	data[i][1]=Math.floor(51+Math.random()*50);
}
const head1=new employee();  //建立第一組串列首
if (!head1) {
	console.log('Error!! 記憶體配置失敗');
	return;
}
head1.num=data[0][0];
head1.name=namedata1[0];
head1.salary=data[0][1];
head1.next=null;
ptr=head1;

for(i=1; i<12; i++){
//建立第一組鏈結串列
	newnode=new employee();
	newnode.num = data[i][0];
	newnode.name=namedata1[i];
	newnode.salary=data[i][1];
	newnode.next=null;
	ptr.next=newnode;
	ptr=ptr.next;
}

for(i=0; i<12; i++){
	data[i][0]=i+13;
	data[i][1]=Math.floor(51+Math.random()*50);
}

const head2=new employee();
if (!head2){
	console.log('Error!! 記憶體配置失敗!!');
	return;
}

head2.num=data[0][0];
head2.name=namedata2[0];
head2.salary=data[0][1];
head2.next=null;
ptr=head2;
for(i=1; i<12; i++){
//建立第二組鏈結串列
  newnode=new employee();
  newnode.num=data[i][0];
  newnode.name=namedata2[i];
  newnode.salary=data[i][1];
  newnode.next=null;
  ptr.next=newnode;
  ptr=ptr.next;
}

i=0;
ptr=concatlist(head1,head2);//將串列相連
console.log('兩個鏈結串列相聯的結果:');
while(ptr!=null){
	process.stdout.write('['+ptr.num+' '+ptr.name+' '+ptr.salary+']=> ');
	i=i+1;
	if(i>3){
		console.log();
		i=0;
	}
	ptr=ptr.next;
}

用一維方式來算

PHP          concatlist.php

$namedata1=array('Allen','Scott','Marry','Jon',
			'Mark','Ricky','Lisa','Jasica',
		   'Hanson','Amy','Bob','Jack');

$namedata2=array('May','John','Michal','Andy',
			'Tom','Jane','Yoko','Axel',
		   'Alex','Judy','Kelly','Lucy');
$namedata=array();

function add_score($arr){
	$count_arr=count($arr);
	$new_arr=array();

	for ($i=0; $i<$count_arr; $i++){
		$score = rand(60,100);
		$temp = $arr[$i].",".$score;
		array_push($new_arr,$temp);
	}
	return $new_arr;
}

function join_arr($arr1,$arr2){
	 $count_arr=count($arr2);
	 for($i=0; $i<$count_arr; $i++){
	 	array_push($arr1,$arr2[$i]);
	 }
	 return $arr1;
}
$new_namedata1=add_score($namedata1);
$new_namedata2=add_score($namedata2);
$namedata=join_arr($new_namedata1,$new_namedata2);

foreach ($namedata as $key => $item) {
	  echo $item." ";
}

後記:

PHP
    使用二維陣列,很難去加、刪,只好用一維陣列並加上符號,來加資料。
       array_push()函數,前面不能加變數,會出錯。
           $my_arr=array_push($my_arr,$add_data);
  會出現錯誤的訊息
      array_push() expects parameter 1 to be array

PHP          concatlist.php

用二維方式來算       

$namedata1=array('Allen','Scott','Marry','Jon',
			'Mark','Ricky','Lisa','Jasica',
		   'Hanson','Amy','Bob','Jack');

$namedata2=array('May','John','Michal','Andy',
			'Tom','Jane','Yoko','Axel',
		   'Alex','Judy','Kelly','Lucy');
$namedata=array();

function add_score($arr){
	$count_arr=count($arr);
	$new_arr=array();
	$temp_arr=array();

	for ($i=0; $i<$count_arr; $i++){
		$score = rand(60,100);
		$temp = $arr[$i];
		$temp_arr=array($temp,$score);
		array_push($new_arr,$temp_arr);
	}
	return $new_arr;
}

function join_arr($arr1,$arr2){
	 $count_arr=count($arr2);
	 for($i=0; $i<$count_arr; $i++){
	 	array_push($arr1,$arr2[$i]);
	 }
	 return $arr1;
}
$new_namedata1=add_score($namedata1);
$new_namedata2=add_score($namedata2);
$namedata=join_arr($new_namedata1,$new_namedata2);

foreach ($namedata as $key => $item) {
	  foreach($item as $value){
	  echo $value." ";
	  }
	  echo "<br>";
}

後記       PHP二維參考資料

PHP      先存成陣列,再用array_push去加
for ($i=0; $i<$count_arr; $i++){
       $score = rand(60,100);
       $temp = $arr[$i];
       $temp_arr=array($temp,$score);
array_push($new_arr,$temp_arr);
}

用佇列(Quene)方式來算

PHP                   concalist_quene.php


5-3-2單向串列插入新節點
在單向鏈結串列中插入新節點,如同一列火車加入新的車廂,有三種情況:加於第1個節點之前、加於最後一個節點之後,以及加於此串列中間任一位置。

演算法如下:

newnode.next=first;
first=newnode;

演算法如下:

ptr.next=newnode;
newnode.next=null;

演算法如下:

newnods.next=x.next;
x.next=newnods;

JS          insert_node.js

class employee {
	constructor() {
		this.num=0;
		this.salary=0;
		this.name='';
		this.next=null;
	}
}

var findnode=(head,num)=>{
	ptr=head;
	while (ptr!=null){
		if (ptr.num==num) return ptr;
		ptr=ptr.next;
	}
	return ptr;
}

var insertnode=(head,ptr,num,salary,name)=>{
	InsertNode=new employee();
	if (!InsertNode) return null;
	InsertNode.num=num;
	InsertNode.salary=salary;
	InsertNode.name=name;
	InsertNode.next=null;
	if(ptr==null)  {  //插入第一個節點
		InsertNode.next=head;
		return InsertNode;
	}
	else{
		if(ptr.next==null)  //插入最後一個節點
				ptr.next=InsertNode;
			else{ //插入中間點
				InsertNode.next=ptr.next;
				ptr.next=InsertNode;
			}
	}
	return head;
}

position=0;
data=[[1001,32367],[1002,24388],[1003,27556],[1007,31299],
			[1012,42660],[1014,25676],[1018,44145],[1043,52182],
			[1031,32769],[1037,21100],[1041,32196],[1046,25776]];
namedata=['Allen','Scott','Marry','John','Mark','Ricky',
					'Lisa','Jasica','Hanson','Daniel','Axel','Jack'];
process.stdout.write('員工編號 薪水\t員工編號 薪水\t員工編號 薪水\t員工編號 薪水\n');
process.stdout.write('-------------------------------------------------------------\n');

for (i=0; i<3; i++) {
	for(j=0; j<4; j++)
		process.stdout.write(data[j*3+i][0]+ '\t' +data[j*3+i][1]+'\t');
		console.log();
}
console.log('-------------------------------------------------------------');

head=new employee(); //建立串列首
head.next=null;

if(!head) {
	console.log('Error!! 記憶體配置失敗');
	return;
}
head.num=data[0][0];
head.name=namedata[0];
head.salary=data[0][1];
head.next=null;
ptr=head;

for (i=1; i<12; i++){//建立串列
	newnode=new employee();
	newnode.next=null;
	newnode.num=data[i][0];
	newnode.name=namedata[i];
	newnode.salary=data[i][1];
	newnode.next=null;
	ptr.next=newnode;
	ptr=ptr.next;
}

while(true) {
	process.stdout.write('請輸入要插入其後的員工編號,如輸入的編號不在此串列中,\n');
	const prompt = require('prompt-sync')();
	const position=parseInt(prompt('新輸入的員工節點將視為此串列的串列首,要結束插入的過程,請輸入-1'));
	if (position ==-1)
		break;
	else{
		ptr=findnode(head,position);
		new_num = parseInt(prompt('請輸入新插入的員工編號:'));
		new_salary=parseInt(prompt('請輸入新插入員工的薪水:'));
		new_name=prompt('請輸入新插入的員工姓名:');
		head=insertnode(head,ptr,new_num,new_salary,new_name);
	}
	console.log();
}
ptr=head;
console.log('\t員工編號     姓名\t薪水');
console.log('\t===========================');
while (ptr!=null) {
	process.stdout.write('\t['+ptr.num+' ]\t[ '+ptr.name+' ]\t['+ptr.salary+']\n');
	ptr=ptr.next;
}

圖說演算法使用JavaScript(八)

5-2陣列與多項式

多項式是數學中相當重要的表現方式,通常如果使用電腦來處理多項式的各種相關運算,可以將多項式以陣列Arrray或鏈結串列Linked List來儲存。本節中,我們還是集中討論多項式以陣列結構表示的相關應用。
5-2-1多項式陣列表示法
這個稱為P(x)為一n次多項式,而一個多項式使用陣列結構儲存在電腦中,可以使用兩種模式。
1.使用一個n+2長度的一維陣列存放,陣列的第一個位置儲存最大的指數n,其他位置依照指數n遞減,依序儲存相對應的係數:
使用這種方法的優點就是在電腦中運用時,對於多項式的各種運算(如加法與乘法)較為方便設計。不過如果多項式的係數為多半為零,就顯得太浪費空間了。
2.只儲存多項式中非零項目。如果有m項非零項目,則使用2m+1長的陣列來儲存每一個非零項目的係數及指數,但陣列的第一個元素則為此多項式非零項的個數。
這種方法的優點是可以節省不必要的記憶空間浪費,但缺點則是在多項式各種多項式演算法設計時,會較為複雜許多。

JS            poly_add.js

ITEMS=6;
var PrintPoly=(Poly,items)=>{
  MaxExp=Poly[0];
  for (i=1; i<Poly[0]+2;i++){
    MaxExp=MaxExp-1;
    if (Poly[i]!=0){
      if(MaxExp+1!=0)
        process.stdout.write(Poly[i]+'X^'+(MaxExp+1)+' ');
      else
        process.stdout.write(' '+Poly[i]);
      if (MaxExp>=0)
        process.stdout.write('+');
    }
  }
}
var PolySum=(Poly1,Poly2)=>{
  result=[];
  result[0]=Poly1[0];
  for(i=1;i<Poly1[0]+2;i++){
    result[i]=Poly1[i]+Poly2[i]; //等冪的係數相加
  }
  PrintPoly(result,ITEMS);
}

PolyA=[4,3,7,0,6,2]; //用方法一,降冪方式敘述:3X^4+7X^3+6X+2
PolyB=[4,1,5,2,0,9]; //用降冪方式敘述:X^4+5X^3+2X^2+9
process.stdout.write('多項式A=> ');
PrintPoly(PolyA,ITEMS);
console.log();
process.stdout.write('多項式B=> ');
PrintPoly(PolyB,ITEMS);
console.log();
process.stdout.write('A+B => ');
PolySum(PolyA,PolyB);

PHP           poly_add.php

$ploy_a=array(4,3,7,0,6,2);
$ploy_b=array(4,1,5,2,0,9);

function get_poly ($arr){
  $len=count($arr);
  $maxexp=$arr[0];
  $re_poly="";

  for ($i=1; $i<$arr[0]+2; $i++){
    
    if ($len !=($arr[0]+2)){
      return "多項式格式錯誤";
      break; //檢驗長度
    }
    
    $maxexp=$maxexp-1;
    if ($arr[$i]!=0){
      if(($maxexp+1)!=0)
        $re_poly=$re_poly.$arr[$i]."X^".($maxexp+1)." "; //字串加法
      else 
        $re_poly=$re_poly." ".$arr[$i];
      
      if($maxexp>=0)
        $re_poly=$re_poly."+";
    }
  }

  return $re_poly; 
  //echo $len."-".$maxexp."<br>";
}

function poly_add($arr_a,$arr_b){
   $result=array();
   $result[0]=$arr_a[0];

   for ($i=1; $i<$arr_a[0]+2; $i++){
   
    if ($arr_a[0] !=$arr_b[0]){
      return "多項式長度錯誤";
      break; //兩個是否不同
    }
    
    $result[$i]=$arr_a[$i]+$arr_b[$i];
   }
   return $result;
}

$get_poly_a = get_poly($ploy_a);
$get_poly_b = get_poly($ploy_b);
$get_poly_add = get_poly(poly_add($ploy_a,$ploy_b));

echo $get_poly_a."<br>";
echo $get_poly_b."<br>";
echo $get_poly_add."<br>";

沒有思考到,級數不一樣時的加法。

如:X^5 + 4X^3 +1   跟 X^4 + 5X^3 + 12X + 5  

圖說演算法使用JavaScript(七)

全方位應用的陣列與串列演算法

5-1-1矩陣相加

請設計一程式來宣告3個二維陣列,來實作2個矩陣相加的過程,並顯示兩矩陣相加後的結果。

JS          matrix_add.js

var A = new Array();
var B = new Array();
var C = new Array();

for (let i=0; i<3; i++){
	A[i]=new Array();
	B[i]=new Array();
	C[i]=new Array();
}

A= [[1,3,5],[7,9,11],[13,15,17]];
B= [[9,8,7],[6,5,4],[3,2,1]];

N=3;
for (let i=0;i<3; i++){
	for (let j=0; j<3; j++){
		C[i][j]=A[i][j]+B[i][j];
	}
}
console.log("[矩陣A和矩陣B相加的結果]");
let str='';
for (let i=0; i<3; i++){
	for (let j=0; j<3; j++){
		str=str+C[i][j]+'\t';
	}
	str=str+'\n';
}
console.log(str);

PHP         matrix_add.php


$A_arr=array(array(1,3,5),array(7,9,11),array(13,15,17));
$B_arr=array(array(9,8,7),array(6,5,4),array(3,2,1));
$C_arr=array();
for ($i=0; $i<count($A_arr); $i++){
	 for($j=0; $j<count($A_arr);$j++){
	 	$C_arr[$i][$j] = $A_arr[$i][$j] + $B_arr[$i][$j];
	 }
}
print_r($C_arr);
echo "<br><hr><br>";
foreach ($C_arr as $item ){
     echo "[";
     $t=1;
     foreach ($item as $value){
      if($t<3)  echo $value.",";
      else echo $value; 
        $t++;
			}
     echo "]<br>";
}

函式說明


foreach ($arr as $yourstr){
echo $yourstr //$yourstr => $arr or $value
}

5-1-2矩陣相乘

如果談到兩個矩陣A與B的相乘,是有某些條件限制。首先必須符合A為一個m*n的矩陣,B為一個n*p的矩陣,對A*B之後的結果為一個m*p的矩陣C。

範例:
請設計一程式來實作下列兩個矩陣的相乘結果。

JS       matrix_multiply.js

const M=2;
const N=3;
const P=2;
A=[6,3,5,8,9,7];
B=[5,10,14,7,6,8];
C=[0,0,0,0];
if (M<=0 || N<=0 || P<=0) console.log('[錯誤:維數M,N,P必須大於0]');
for (let i=0; i<M; i++){
	for (let j=0; j<P; j++){
		let Temp=0;
	for (let k=0; k<N; k++) Temp = Temp +parseInt(A[i*N+k])*parseInt(B[k*P+j]);
			C[i*P+j] = Temp;
  }
}
console.log('[AxB的結果是]');
let str='';
for (i=0; i<M; i++){
	for (j=0; j<P; j++){
		str = str+C[i*P+j]+ '\t';
	}
	str = str+'\n';
}
console.log(str);

PHP            matrix_multiply.php

$a_arr = array(array(6,3,5),array(8,9,7));
$b_arr = array(array(5,10),array(14,7),array(6,8));

$a_m = count($a_arr);    //陣列 M列
$a_n = count($a_arr[0]); //陣列 N行

$b_n = count($b_arr);    //陣列 N列
$b_p = count($b_arr[0]); //陣列 P行

$c_arr = array();
if ($a_m <=0 || $a_n <=0 || $b_n<=0 || $b_p<=0 || $a_n!=$b_n){
  echo "條件錯誤";
}
    
//echo "a_m=".$a_m."; a_n=".$a_n."<br>";
//echo "b_n=".$b_n."; b_p=".$b_p."<br>";

for ($i=0 ; $i<$a_m; $i++){
    for ($j=0; $j<$b_p; $j++){
      $temp=0;
     for ($k=0 ; $k<$a_n; $k++){
         $temp = $temp + intval($a_arr[$i][$k])*intval($b_arr[$k][$j]);
         //echo $temp."<br>";
         $c_arr[$i][$j]=$temp;
      }
         //echo $temp."<br>";
   }
    //echo "--<br>";
}
print_r($c_arr);

5-1-3轉置矩陣
「轉置矩陣」At就是把原矩陣的行座標元素相互調換,假設At為A的轉置矩陣,則有At[j,i]=[i,j],如下圖所示:

範例

    請設計一程式來實作一4*4二微陣列的轉置矩陣。

JS          transpose.js

arrA=[[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]];
N=4;
arrB=[[],[],[],[]];
console.log('原設定的矩陣內容');
for (i=0; i<4; i++){
	str = '';
	for (j=0; j<4; j++){
		str= str+arrA[i][j]+'\t';
	}
	console.log(str);
}

for(i=0; i<4; i++){
	for(j=0; j<4; j++){
		arrB[i][j]=arrA[j][i];
	}
}
console.log('[轉置矩陣的內容為]');
for (i=0; i<4; i++){
	str = '';
	for (j=0; j<4; j++){
		str= str+arrB[i][j]+'\t';
	}
	console.log(str);
}

PHP          transpose.php

$a_arr = array(array(1,2,3,4),array(5,6,7,8),array(9,10,11,12),array(13,14,15,16));
$b_arr = array();

$arr_m = count($a_arr);
$arr_n = count($a_arr[0]);

echo "M-".$arr_m."N-".$arr_n."<br>";

for ($i=0; $i<$arr_m; $i++){
  for($j=0; $j<$arr_n; $j++){
      $b_arr[$i][$j]=$a_arr[$j][$i];
  }
}

foreach($a_arr as $item){
   foreach ($item as $value){
    echo $value."   ";
   }
   echo "<br>";
}

foreach($b_arr as $item){
   foreach ($item as $value){
    echo $value."   ";
   }
   echo "<br>";
}
5-1-4 稀疏矩陣
稀疏矩陣最簡單的定義就是一個矩陣中大部分的元素為0,即可稱為「稀疏矩陣」Sparse Matrix。例如下列的矩陣就是典型的稀疏矩陣。
當然如果直接使用傳統的二維陣列來儲存上圖的稀疏矩陣也是可以,但事實上許多元素都是0。這樣的做法在矩陣很大時的稀疏矩陣,就會十分浪費記憶體空間。而改進空間的方法就是利用三項式(3-tuple)的資料結構。我們把每一個非零項目以(i,j,item-value)來表示。更詳細的形容,就是假如一個稀疏矩陣有n個非零項目,那麼可以利用一個A(0:n,1:3)的二維陣列來表示。
其中A(0,1)代表此稀疏矩陣的列數,A(0,2)代表此稀疏矩陣的行數,而A(0,3)則是此稀疏矩陣非零項目的總數。另外每一個非零項目以(i,j,item-value)來表示。其中i為此非零項目所在的列數,j為此非零項目所在行數,item-value則此分零項的值。以上圖6*6稀疏矩陣為例,則可以以下表示:
A(0,1)=>表示此舉矩陣的列數。
A(0,2)=>表示此舉矩陣的行數。
A(0,3)=>表示此舉矩陣非零項目的總數。
這種利用3項式(3-tuple)資料結構來壓縮稀疏矩陣,可以減少記憶體不必要的浪費。

JS          sparse.js

var NONZERO=0;
temp=1;
Sparse=[[15,0,0,22,0,-15],[0,11,3,0,0,0],
        [0,0,0,-6,0,0],[0,0,0,0,0,0],
        [91,0,0,0,0,0],[0,0,28,0,0,0]];//宣告稀疏矩陣,稀疏矩陣的所有元素設為0
str='';
console.log('[稀疏矩陣的各個元素]');
for (i=0;i<6;i++) {
   for(j=0;j<6;j++){
      process.stdout.write(Sparse[i][j]+'\t');
      if (Sparse[i][j]!=0) NONZERO=NONZERO+1;
   }
   console.log();
}

Compress=new Array();   //宣告壓縮陣列
for (let i=0; i<NONZERO+1; i++) Compress[i]=[];

//開始壓縮稀疏矩陣
Compress[0][0]=6;
Compress[0][1]=6;
Compress[0][2]= NONZERO;

for (i=0; i<6; i++){
   for (j=0; j<6; j++){
      if (Sparse[i][j]!=0){
         Compress[temp][0]=i;
         Compress[temp][1]=j;
         Compress[temp][2]=Sparse[i][j];
         temp=temp+1;
      }
   }
}
console.log('[稀疏矩陣壓縮後的內容]');//印出壓縮矩陣
for (i=0; i<NONZERO+1;i++){
   for (j=0; j<3; j++){
      process.stdout.write(Compress[i][j]+'\t');
   }
   console.log();
}

PHP            sparse.php

$sparse=array(array(15,0,0,22,0,-15),array(0,11,3,0,0,0),
              array(0,0,0,-6,0,0),array(0,0,0,0,0,0,0),
              array(91,0,0,0,0,0),array(0,0,28,0,0,0));
$M = count($sparse);
$N = count($sparse[0]);
$nonzero=0;//計算沒有為零的數量
$temp=1;
$compress=array();

for ($i=0; $i<$N; $i++){
  for($j=0; $j<$N; $j++){
    if ($sparse[$i][$j]!=0)
      $nonzero=$nonzero+1;
  }
}
echo "原始陣列"."<br>";
foreach ($sparse as $key => $item) {
    foreach($item as $list => $value){
      echo $value."   ";
    }
      echo "<br>";
}

$compress[0][0]=$M;
$compress[0][1]=$N;
$compress[0][2]=$nonzero;

for($i=0; $i<$N; $i++){
  for($j=0; $j<$M; $j++){
    if ($sparse[$i][$j]!=0){
      $compress[$temp][0]=$i;
      $compress[$temp][1]=$j;
      $compress[$temp][2]=$sparse[$i][$j];
      $temp=$temp+1;
    }
  }
}
echo "壓縮後的陣列<br>";

foreach ($compress as $key => $item) {
    foreach($item as $list => $value){
      echo $value."   ";
    }
      echo "<br>";
}