圖說演算法使用JavaScript(十六)

7-4插入排序法

插入排序法insert sort則是將陣列中的元素,逐一與已排序好的資料作比較,如前兩個元素先排好,再將第三個元素插入適當的位置,所以這三個元素仍然是已排序好,接著再將第四個元素加入,重複此步驟,直到排序完成為止。各位可以看做是在一串有序的紀錄R1、R2..Ri,插入新的紀錄R,使得i+1個紀錄排序妥當。

以下利用55、23、87、62、16數列的由小到大排序過程,來說明插入排序法的演算流程。下圖中,在步驟二,以23為基準與其他元素比較後,放到適當位置(55的前面),步驟三則拿87與其他兩個元素比較後,接著62在比較完前三個數後插入87的前面…,將最後一個元素比較完後即完成排序:

JS          insert.js

let SIZE=8;
	//定義陣列大小
var showdata=(data)=> {
	for (k=0; k<8; k++) {
		process.stdout.write(data[k]+' ');
	}
}

var insert=(data)=> {
	for (i=0; i<SIZE; i++) {
		tmp=data[i];
		//tmp用來暫存資料
		no=i-1;
		while (no>=0 && tmp<data[no]) {
			data[no+1]=data[no];
			//就把所有元素往後推一個位置
			no-=1;
		}
		data[no+1]=tmp;
		//最小的元素放到第一個元素
	}
}
data=[16,25,39,27,12,8,45,63];
console.log('原始陣列是:');
showdata(data);
insert(data);
console.log();
console.log('排序後陣列是:');
showdata(data);

PHP          insert.php

$data=array(16,25,39,27,12,8,45,63);
$count=count($data);

echo "原始陣列:<br>";
showdata($data);

$new_data=insert($data);

echo "<br>排列後的陣列:<br>";
showdata($new_data);

function insert($arr) {
	$count=count($arr);
	//計算陣列大小
	for ($i=0; $i<$count; $i++) {
		$tmp=$arr[$i];
		//用tmp來暫存資料
		$no=$i-1;
		while ($no>=0 && $tmp<$data[$no]){
			$data[$no+1]=$data[$no];
			//就把所有元素往後推一個位置
			$no--;
		}
		$data[$no+1]=$tmp;
		//最小的元素放到第一個元素  
	}
	return $data;
}


function showdata($arr){
	foreach($arr as $key=>$value){
		echo $value."、";
	}
	echo "<br>";
}

7-5謝耳排序法

我們知道如果原始紀錄的鍵值大部分以排序好的情形下,插入排序法或非常有效率,因為它無須做太多的資料搬移動作。[謝耳排序法]是D.L.Shell在1959年7月所發明的一種排序法,可以減少插入排序法中資料搬移的次數,以加速排序進行。排序的原理是將資料分成特定間隔的幾個小區塊,以插入排序法排完區塊內的資料後再漸漸減少間隔的距離。

以下利用63、92、27、36、45、71、58、7數列的由小到大排序過程,來說明謝耳排序法的演算過程。

範例:shell.js>請設計一程式,並使用謝耳排序法來將以下的數列排序:
16,25,39,27,12,8,45,63

JS           shell.js

let SIZE=8;

var showdata=(data)=> {
	for (i=0; i<SIZE; i++) {
		process.stdout.write(data[i]+ ' ');
	}
	console.log();
}

var shell=(data, size)=> {
	k=1;
	//k列印計數
	jmp=parseInt(size/2);
	while (jmp !=0) {
		for (i=jmp; i<size-1; i++) {
			//i為掃描次數 jmp為設定間距位移量
			tmp=data[i];
			//tmp用來暫存資料
			j=i-jmp;
			//以j來定位比較的元素
			while (tmp<data[j] && j>=0) {
				//插入排序法
				data[j+jmp] = data[j];
				j=j-jmp;
			}
			data[jmp+j]=tmp;
		}
		process.stdout.write('第 '+k+' 次排序過程:');
		k=k+1;
		showdata(data);
		console.log('------------------------------------');
		jmp=parseInt(jmp/2);
		//控制迴圈數
	}
}

data=[16,25,39,27,12,8,45,63];
process.stdout.write('原始陣列是:     ');
showdata(data);
console.log('-------------------------------------');
shell(data, SIZE);

圖說演算法使用JavaScript(十五)

7-2氣泡排序法

氣泡排序法又稱為交換排序法,是由觀察水中氣泡變化構思而成,原理是由第一個元素開始,比較相鄰元素大小,如果大小順序有誤,則對調後在進行下一個元素的比較,就彷彿氣泡逐漸由水底逐漸升到水面上一樣。如此掃描過一次之後就可確保最後一個元素是位於正確的順序。接著再逐步進行第二次掃描,直到完成所有元素的排序關係為止。

以下排序我們利用55、23、87、62、16的排序過程,您可以清楚知道氣泡排序法的演算流程:

由此可知5個元素的氣泡排序法必須執行5-1次掃描,第一次掃描需比較5-1次,共比較4+3+2+1=10次。

JS               bubble.js

var data=[16, 25, 39, 27, 8 ,45, 63];   
//原始資料
console.log('氣泡排序法:原始資料為:');
for (i=0; i<8; i++) 
	process.stdout.write(data[i]+' ');

console.log();

for (i=7; i>0; i--) {    //掃描次數
	for (j=0; j<i; j++) {
		if(data[j]>data[j+1])  {     
		//比較,交換的次數
			temp=data[j];
			data[j]=data[j+1];      
			//比較相鄰兩數,如果第一數較大則交換
			data[j+1]=temp;
		}
	}
	process.stdout.write('第 ' +(8-i)+ ' 次排序後的結果是:')    
	//把各次結果掃描出來
	for (j=0; j<8; j++) process.stdout.write(data[j]+' ');
		console.log();
}
console.log('排序後結果為:');
for (j=0; j<8; j++) process.stdout.write(data[j]+ ' ');
console.log(); 

PHP          buble.php

$data = array("16","25","39","27","8","45","63");
//原始資料
echo "原本陣列是:";
arr_print($data);
echo "<br><br>";

$count=count($data);
for ($i=$count-1; $i>0; $i--){
	//掃描次數
	for ($j=0; $j<$i; $j++){
		//比較,交換的次數
		if ($data[$j]>$data[$j+1]){
			$temp=$data[$j];
			$data[$j]=$data[$j+1];
			//比較相鄰兩數,如果第一數較大則交換
			$data[$j+1]=$temp;
		}

	}
	$time=7-$i;
	echo "第{$time}次";
	arr_print($data);
	echo "<br>";
}
echo "<br><br>";
echo "排完陣列是:";
arr_print($data);

function arr_print($arr){
	foreach ($arr as $key => $value) {
		echo $value." ";
	  	echo "--";
	}
}

7-3選擇排序法

選擇排序法Selection Sort也算是枚舉法的應用,概念就是反覆從未排序的數列中取出最小的元素,加入到另一個數列,結果即為已排序的數列。選擇排序法可使用兩種方式排序,一圍在所有的資料中,當由大至小排序時,則將最大值放入第一位置;若由小至大排序時,則將最大值放入為至末端。例如一開始在所有的資料中挑選一個最小項放在第一個位置(假設是由小到大),再從第二筆開始挑選一個最小項放在第2個位置,依樣重複,直到完成排序為止。

以下利用55、23、87、62、16數列的由小到大排序過程,來說明選擇排序法的演算過程:

JS          select.js

var showdata=(data)=>   {
	for (k=0; k<8; k++){
		process.stdout.write(data[k]+' ');
	}
}

var select=(data)=> {
	for(i=0; i<7; i++) {
		smallest=data[i];
		index=i;
		for (j=i+1; j<8; j++) {
			//由i+1比較起
			if (smallest>data[j]) {
			//找出最小元素
			smallest=data[j];
			index=j;	
			}	
		}
	tmp=data[i];
	data[i]=data[index];
	data[index]=tmp;
	console.log();
	process.stdout.write('第'+(i+1)+"次排序結果: ");
	showdata(data);
	}
}

data=[63,25,39,27,12,8,45,16];
process.stdout.write('原始資料為:');
for (i=0; i<8; i++) process.stdout.write(data[i]+' ');
	console.log();
	console.log("----------------------------");
	select(data);
	console.log();
	console.log("----------------------------");
	process.stdout.write("排序後資料");
for (i=0; i<8; i++) process.stdout.write(data[i]+'  ');
	console.log();
	console.log("----------------------------"); 

PHP          select.php

$data = array("16","25","39","27","8","45","63");
//原始資料
echo "原本陣列是:";
arr_print($data);
echo "<br><br>";

$count=count($data);

for ($i=0; $i<$count-1; $i++) {
	$smallest=$data[$i];
	$index=$i;
		for ($j=$i+1; $j<$count; $j++) {
					//由i+1比較起
			if($smallest>$data[$j]){
					//找出最小元素
				$smallest=$data[$j];
				$index=$j;
			}
		}
	$tmp=$data[$i];
	$data[$i]=$data[$index];
	//兩個交換
	$data[$index]=$tmp;
	//把$tmp換回來
	$num=$i+1;
	echo "第{$num}次排序結果:";
	arr_print($data);
	echo "<br>";
	
}

echo "<br><br>";
echo "排完陣列是:";
arr_print($data);

function arr_print($arr){
	foreach ($arr as $key => $value) {
		echo $value." ";
	  	echo "--";
	}
}

圖說演算法使用JavaScript(十四)

6-6鏈結串列實作佇列

佇列除了能以陣列的方式來實作外,我們也可以鏈結串列來實作佇列。在宣告佇列類別中,除了和佇列類別中相關的方法外,還必須有指向佇列前端及佇列尾端的指標,即front及rear。例如我們以學生姓名及成績的結構資料來建立佇列串列的節點,及front與rear指標宣告如下:

class student  {
constructor() {
this.name='';
this.score=0;
this.next=null;
}
}
front=new student();
rear=new student();
front=null;
rear=null;

至於在佇列串列中加入新節點,等於加入此串列的最後端,而刪除節點就是將此串列最前端的節點刪除。加入與刪除運算法如下:

var enqueue=(name, score)=> {  //置入佇列資料
new_data= new student(); //配置記憶體給新元素
new_data.name=name; //設定新元素的資料
new_data.score = score;
if (rear==null) //如果rear為null,表示這是第一個元素
front = new_data;
else
rear.next = new_data; //將新元素連接至佇列尾端
rear = new_data; //將rear指向新元素,這是新的佇列尾端
new_data.next = null; //新元素之後無其他元素
}
var dequeue=()=>{              // 取出佇列資料
if (front == null)
process.stdout.write('佇列已空!\n');
else {
process.stdout.write('姓名:'+front.name+'\t成績:'+front.score+'...取出'); //將佇列前端一致下一個元素
}
}

JS          list_queue.js

class student {
	constructor () {
		this.name='';
		this.score=0;
		this.next=null;
	}
}
front=new student();
rear=new student();
front=null;
rear=null;

var enqueue=(name, score)=> {  //置入佇列資料
	new_date=new student();    //配置記憶體給新元素
	new_date.name=name;
	new_date.score=score;
	if (rear==null)            //如果rear為null,表示這是第一個元素
		front=new_date;
	else
		rear.next=new_date;    //將新元素連接至佇列尾端
	rear=new_date;             //將rear指向新元素,這是新的佇列尾端
	new_date.next = null;      //新元素之後無其他元素
}

var dequeue=()=>{    //取出佇列資料
	if(front == null)
		process.stdout.write('佇列已空!\n');
	else {
		process.stdout.write('姓名:'+front.name+'\t成績:'+front.score+'....取出\n');
		front = front.next;    //將佇列前端移至下一個元素
	}

}

var show=()=> {    //顯示佇列資料
	ptr = front;
	if (ptr == null)
		process.stdout.write('佇列已空!\n');
	else {
		while (ptr !=null)  {  //由front往rear走訪佇列
			process.stdout.write('姓名:'+ptr.name+'\t成績:'+ptr.score+'\n');
			ptr = ptr.next;
		}

	}
}

select=0;
const prompt = require('prompt-sync')();
while (true) {
	const select = parseInt(prompt('(1)存入 (2)取出 (3)顯示 (4)離開=> '));
	if (select==4)
		break;
	if (select==1) {
		const name = prompt('姓名: ');
		const score = parseInt(prompt('成績: '));
		enqueue(name, score);
	}
	else if (select==2)
		dequeue();
	else
		show();
}

6-7有趣的雙向佇列

所謂雙向佇列Double Ended Queue, Deque 為一有序串列,加入與刪除可在佇列的任一端進行,請看下圖:

具體來說,雙向佇列就是允許兩端中任意一端都具備有刪除或加入功能,而且無論與尾端指標都是朝佇列中央來移動。通常在一般的應用上,雙向佇列可以區分為兩種:第一種是資料只能從一端加入,但是可從兩端取出。另一種則是可由兩端加入,但由一端取出。以下我們將討論第一種輸入限制的雙向佇列的節點宣告、加入與刪除運算法如下:

class Node   {
      constructor() {
this.data=0;
this.next=null;
}
}
front=new Node();
rear=new Node();
front=null;
rear=null;
//方法enqueue:佇列資料的存入
var enqueue=(value)=> {
node = new Node();
node.data=value;
node.next=null; //檢查是否為空佇列
if (rear==null)
fornt=node; //新建立的節點成為第一個節點
else
rear.next=node; //將節點加入到佇列的尾端
rear=node;
}
//方法dequeue:佇列資料的取出
var dequeue=(action)=> {
//從前端取出資料
if (!(front==null) && action==1) {
if (front==rear) rear=null;
value=front.data;
front=front.next;
return value;
}
//從尾端取出資料
else if (!(rear==null) && action=2) {
startNode=front; //先記下前端的指標值
value=rear.data; //取出目前尾端的資料
//尋找最尾端節點的前一個節點
tempNode=front;
while (front.next!=rear && front.next!=null){
front=front.next;
tempNode=front;
}
front=startNode; //記錄從尾端取出資料後的佇列前端指標
rear=tempNode; //紀錄從尾端取出資料後的佇列尾端指標
//下一列程式是指當佇列中僅剩下最後節點時
//取出資料後便將 front 及 rear 指向 null
if (front.next==null || rear.next==null) {
front=null;
rear==null;
}
return value;
}
else return -1;
}

JS         dequeue.js 

class student {
	constructor () {
		this.name='';
		this.score=0;
		this.next=null;
	}
}
front=new student();
rear=new student();
front=null;
rear=null;

var enqueue=(name, score)=> {  //置入佇列資料
	new_date=new student();    //配置記憶體給新元素
	new_date.name=name;
	new_date.score=score;
	if (rear==null)            //如果rear為null,表示這是第一個元素
		front=new_date;
	else
		rear.next=new_date;    //將新元素連接至佇列尾端
	rear=new_date;             //將rear指向新元素,這是新的佇列尾端
	new_date.next = null;      //新元素之後無其他元素
}

var dequeue=()=>{    //取出佇列資料
	if(front == null)
		process.stdout.write('佇列已空!\n');
	else {
		process.stdout.write('姓名:'+front.name+'\t成績:'+front.score+'....取出\n');
		front = front.next;    //將佇列前端移至下一個元素
	}

}

var show=()=> {    //顯示佇列資料
	ptr = front;
	if (ptr == null)
		process.stdout.write('佇列已空!\n');
	else {
		while (ptr !=null)  {  //由front往rear走訪佇列
			process.stdout.write('姓名:'+ptr.name+'\t成績:'+ptr.score+'\n');
			ptr = ptr.next;
		}

	}
}

select=0;
const prompt = require('prompt-sync')();
while (true) {
	const select = parseInt(prompt('(1)存入 (2)取出 (3)顯示 (4)離開=> '));
	if (select==4)
		break;
	if (select==1) {
		const name = prompt('姓名: ');
		const score = parseInt(prompt('成績: '));
		enqueue(name, score);
	}
	else if (select==2)
		dequeue();
	else
		show();
}

圖說演算法使用JavaScript(十三)

6-4八皇后演算法

         八皇后問題也是一種常見的堆疊應用實例。在西洋棋中的皇后可以在沒有限定一步走幾格的前提下,對其盤中的其他棋子直吃、橫吃及對角斜吃(左斜吃或右斜吃皆可),只要後放入的新皇后,再放入前必須考慮所放置直線方向、橫線方向或對角線方向是否已被放置就皇后,否則就會被先放入的舊皇后吃掉。
        利用這個觀念,我們可以將其應用在4*4的棋盤,就稱為4-皇后問題;應用在8*8的棋盤,就稱為8-皇后問題。應用在N*N的棋盤,就稱為N-皇后問題。要解決N-皇后問題(在此我們以8-皇后為例),首先當於棋盤中置入一新皇后,且這個位置不被先前放置的皇后吃掉,則將此新皇后的位置存入堆疊。
        但若欲放置新皇后的該行(或該列)的8個位置,都沒有辦法放置新皇后(亦即一放入任何一個位置,就會被先前放置的舊皇后給吃掉)。此時,就必須由堆疊中取出前一個皇后的位置,並於該行(或該列)中重新尋找另一個新的位置放置,在將該位置存入堆疊中,而這種方式就是一種回溯Backtracking)演算法的應用概念。
          N-皇后問題的解答,就是配合堆疊及回溯兩種演算概念,以逐行(或逐列)找新皇后位置(如果找不到,則回溯到前一行找尋前一個皇后另一個新位置,以此類推)的方式,來尋找N-皇后問題的其中一組解答。

JS                queen.js

程式碼:如下

const EIGHT=8;  //定義最大堆疊容量
queen = [];  //存放8個皇后之列位
number=0;   //計算總共幾組解的總數
//決定皇后存放的位置
//輸出所需要的結果

var print_table=()=> {
	let x=y=0;
	number+=1;
	process.stdout.write('\n');
	process.stdout.write('八皇后問題的第'+number+'組解\n\t');
	for (x=0; x<EIGHT ; x++) {
		for (y=0; y<EIGHT ; y++){
			if (x==queen[y])
				process.stdout.write('<q>');
			else 
				process.stdout.write('<->');
		}
		process.stdout.write('\n\t');
	}
}

//測試在(row,col)上的皇后是否遭受攻擊
//若遭受攻擊則傳回值為1,否則傳回0
var attack=(row, col)=>{
	let i=0;
	atk=false;
	offset_row=offset_col=0;
	while ((atk!=1) && i<col) {
		offset_col=Math.abs(i-col);
		offset_row=Math.abs(queen[i]-row);
		//判斷兩皇后是否在同一對角線上
		if ((queen[i]==row || offset_row==offset_col)) 
			atk=true;
		i=i+1;
	}
	return atk;
}

var decide_position=(value)=>{
	let i=0;
	while (i<8) {
		//是否受到攻擊攻擊判斷式
		if (attack(i,value)!=1) {
			queen[value]=i;
			if (value==7)
				print_table();
			else
				decide_position(value+1);
		}
		i++;
	}
}

//主程式
decide_position(0);

6-5 陣列實作佇列

         以陣列結構來製作佇列的好處是演算法相當簡單,不過與堆疊不同之處是需要擁有兩種基本動作加入與刪除,而且使用frint與rear兩個註標來分別指向佇列的前端與尾端,缺點是陣列大小並無法事先規劃宣告。首先我們需要宣告一個有限容量的陣列,並以下列說明:

const MAXSIZE=4;
queue=[];       //佇列大小為4
front=-1;
rear=-1;

JS          array_queue.js

const MAX=10;      //定義佇列的大小
queue=[];
var front=rear=-1;
var choice='';
const prompt = require ('prompt-sync')();
while (rear<MAX-1 && choice !='e') {
	const choice = prompt('[a]表示存入一個數值[d]表示取出一個數值[e]表示跳出此程式: ');
	if (choice=='a') {
		const val = parseInt(prompt('[請輸入數值]: '));
		rear+=1;
		queue[rear]=val;
	}
	else if (choice=='d') {
		if (rear>front) {
			front+=1;
			process.stdout.write('[取出數值為]: ' +queue[front]+'\n');
			queue[front]=0;
		}
		else{
			process.stdout.write('[佇列已經空了]\n');
			return;
		}
	}
	else {
		process.stdout.write('\n');
		break;
	}
}
process.stdout.write('---------------------------\n');
process.stdout.write('[輸出佇列中所有元素]: \n');

if (rear==MAX-1)
	process.stdout.write('[佇列已滿]\n');
else if (front>=rear)  {
	process.stdout.write('沒有\n');
	process.stdout.write('[佇列已空]\n');
}
else  {
	while (rear>front) {
		front+=1;
		process.stdout.write('['+queue[front]+'] ');
	}
	process.stdout.write('\n');
	process.stdout.write('---------------------------------------------\n');
}
process.stdout.write('\n');

PHP          array_queue.php 

$choice="n";
$queue=$_SESSION['queue'];
$length = length($queue);

if($_GET['choice'])
$choice=$_GET['choice'];

echo "<center>[<a href=".$_SERVER['PHP_SELF']."?choice=a>A</a>]表示存入一數值|
							[<a href=".$_SERVER['PHP_SELF']."?choice=d>D</a>]表示取出一數值|
							[<a href=".$_SERVER['PHP_SELF']."?choice=e>E</a>]表示跳出此程式|
							[<a href=".$_SERVER['PHP_SELF']."?choice=n>N</a>]清空佇列
			</center>";


if($choice)
	switch ($choice){

		case 'a':
		echo "
		<center>
		 <form method='post' action={$_SERVER['[PHP_SELF]']}>
		 請輸入數值:
		 <input name='add_queue' type='text'>
		 <input type='submit' name='submit' value='送出''>
     </form>
    </center>
		";
		break;

		case 'd':
		$queue=$_SESSION['queue'];      //取出
		$del=$queue[0];
		array_shift($queue);
		echo "<center> 刪除 {$del} </center>";
		$_SESSION['queue']=$queue;	      //存入
		break;

		case 'e':
		//$length = length($queue);
    echo "
    <center>目前佇列數目:{$length}</center>
		";
		break;

		case 'n':
		unset($_SESSION['queue']);
		session_destroy();
		break;

	}


if ($_POST['submit']=='送出'){
	//echo $_POST['add_queue'];
	$queue=array();      //先宣告陣列
	//print_r($queue)."<br>";
	//print_r($_SESSION['queue'])."<br>";
	
	$queue = $_SESSION['queue'];       //取出
	//array_push ($queue, $_POST['add_queue']);  //不知為什麼會出現錯誤,但是queue有值後就不會出現
	$queue[]=$_POST['add_queue'];     //加入陣列
	$_SESSION['queue']=$queue;      //存入
}


echo "<center>目前陣列"."<br>";
//print_r($queue);
//echo "here";
foreach ($_SESSION['queue'] as $value){
		echo $value."  ";
	}

function length ($arr){
	return count($arr);
}	
$queue=array();
$queue = $_SESSION['queue']; //取出
$queue[]=$_POST['add_queue']; //加入陣列

可以改寫為
$_SESSION['queue'][]=$_POST['add_queue'];

圖說演算法使用JavaScript(十二)

6-3古老的河內塔演算法

法國數學家Lucas在1883年介紹了一個十分經典的河內塔Tower of Hanoli智力遊戲,是典型使用遞迴式與堆疊觀念來解決問題的範例,內容是說在古印度神廟,廟中有三根木樁,天神希望和尚們把某些數量大小不同的圓盤,由第一個木樁全部移動到第三個木樁。

更精確來說,河內塔問題可以這樣形容:假設有A、B、C三個木樁和n個大小均不同的套環Disc,由小到大編號為1,2,3,…n,編號越大直徑越大。開始的時候,n個套環套進A木樁上,現在希望找到將A木樁上的套環藉著B木樁當中間橋樑,全部移到C木樁上最少次數的方法。不過在搬動時還必須遵守下列規則:

1.直徑較小的套環永遠置於直徑較大的套環上。
2.套還可任意弟由一個木樁移到其他的木樁上。
3.每一次僅能移動一個套環,而且只能從最上面的套環開始移動。

現在我們考慮n=1~3的狀況,以圖示方式為大家示範處理河內塔問題的步驟:

結論:移動了2@2-1=3次,盤子移動的次序為1,2,1(此處為盤子次序)
步驟:1->2,1->3,2->3(此處為木樁次序)

結論:移動了2@3-1=7次,盤子移動的次序為1,2,1,3,1,2,1(盤子次序)。
步驟為1->3,1->2,3->2,1->3,2->1,2->3,1->3(木樁次序)

當有4個盤子時,我們實際操作後(在此不作圖說明),盤子移動的次序為121312141214121,而移動木樁的順序為1->2,1->3,2->3,1->2,3->1,3->2,1->2,1->3,2->3,2->1,3->1,2->3,1->2,1->3,2->3,而移動次數為2@4-1=15。
當n不大時,各位可以逐步用圖示解決,但n的值較大時,那就十分傷腦筋了。

事實上,我們可以得到一個結論,例如當有n個盤子時,可將河內塔問題歸納成三個步驟:歸納成三個步驟:

1.將n-1個盤子,從木樁移動到木樁2。
2.將第n個最大盤子,從木樁1移動到木樁3。
3.將n-1個盤子,從木樁2移動到木樁3。

由上圖中,應該發現河內塔問題是非常適合以遞迴式與堆疊來解決。因為它滿足了遞迴的兩大特性1.有反覆執行的過程、2.有停止的出口。以下則以遞迴式來表示河內塔遞迴函數的演算法。

var hanoi=(n, p1, p2, p3)=>{
         if (n==1) //遞迴出口
                         process.stdout.write(‘套環從 ‘+p1+ ‘移到 ‘+p3+’\n’);
         else {
                         hanoi(n-1, p1, p3, p2);
                         process.stdout.write(‘套環從 ‘+p1+’ 移到 ‘+p3+’\n’);
                         hanoi(n-1, p2, p1, p3);
             }
}

JS          haoni.js

var hanoi=(n, p1, p2, p3)=> {
	if (n==1) //遞迴出口
		process.stdout.write('套環從'+p1+'移到 '+p3+'\n');
	else {
		hanoi(n-1, p1, p3, p2);
		process.stdout.write('套環從 '+p1+'移到' +p3+'\n');
		hanoi(n-1, p2, p1, p3);
	}
}
const prompt = require('prompt-sync')();
const j= parseInt(prompt('請輸入所移動套環數量:'));
hanoi(j,1,2,3);

PHP          haoni.php

$n=3;
echo "您所輸入的N是{$n}<br>";
haoni($n,1,2,3);

function haoni ($j, $p1, $p2, $p3){

	switch ($j){
		case 1;
		  	echo "套環從{$p1}移到{$p3}<br>";
		    break;
		default:
			haoni($j-1,$p1, $p3, $p2);
		  	echo "套環從{$p1}移到{$p3}<br>";
		  haoni($j-1,$p2, $p1, $p3);
	}

}

圖說演算法使用JavaScript(十一)

        堆疊結構在電腦中的應用相當廣泛,時常被用來解決電腦的問題,例如前面所談到的遞迴呼叫、副程式的呼叫,至於在日常生活中的應用也隨處可以看到,例如大樓電梯、貨架上的貨品等等,都是類似堆疊的資料結構原理。
        佇列在電腦領域的應用也相當廣泛,例如計算機的模擬simulation、CPU的工作排程Job Scheduling、線上同時周邊作業系統的應用與圖形走訪得先廣後深搜尋法BFS。由於堆疊與佇列都是抽象資料型態Abstract Data Type,ADT,本章終將為各位介紹相關演算法。
         堆疊在程式設計領域中,包含以下兩種設計方式,分別為陣列結構與鏈結串列結構。

6-1陣列實作堆疊輕鬆學

         以陣列結構來製作堆疊的好處是製作與設計的演算法都相當簡單,但因為如果堆疊本身是變動的話,大小並無法事先規劃宣告,太大時浪費空間,太小則不夠使用。

相關演算法如下
//判斷是否空堆疊
var isEmpy=()=>{
if (top==-1)
return true;
else
return false;
}
//將指定的資料存入堆疊
var push=(data)=>{
if (top>=MAXSTACK-1)
process.stdout.write('堆疊已滿,無法再加入');
else {
top +=1;
stack[top]=data; //將資料存入堆疊
}
}
//從堆疊取出資料
var pop=()=>{
if (isEmpty())
process.stdout.write('堆疊是空');
else {
process.stdout.write('彈出的元素為: '+stack[top]+'\n');
top=top-1;
}
}

JS            array_stack.js

const MAXSTACK=100; //定義最大堆疊容量
stack=[]; //堆疊的陣列宣告
top=-1; //堆疊的頂端

//判斷是是否為空堆疊
var isEmpty=()=>{
	if(top==-1)
		return true;
	else
		return false;
}

//將指定的資料存入堆疊
var push=(data)=> {
	if (top>=MAXSTACK-1)
		process.stdout.write('堆疊已滿,無法再加入');
	else {
		top +=1;
		stack[top]=data;//將資料存入堆疊
	}
}

//從堆疊取出資料
var pop=()=> {
	if (isEmpty())
		process.stdout.write('堆疊是空');
	else {
		process.stdout.write('彈出的元素為:'+stack[top]+'\n');
		top=top-1;
	}
}

//主程式
i=2;
count=0;
const prompt = require('prompt-sync')();
while(true) {
	const i = parseInt(prompt('要推入堆疊,請輸入1,彈出則輸入0,停止操作則輸入-1:'));
	if (i==-1)
		break;
	else if (i==1) {
		const value = parseInt(prompt('請輸入元素值:'));
		push(value);
	}
	else if (i==0)
		pop();
}

process.stdout.write('========================\n');

if (top<0)
	process.stdout.write('\n 堆疊是空的\n');
else {
	i=top;
	while (i>=0) {
		process.stdout.write('堆疊彈出的順序為:'+stack[i]+'\n');
		count +=1;
		i =i-1;
	}
}
process.stdout.write('=====================================\n');

PHP          array_stack.php

6-2鏈結串列實作堆疊

        使用鏈結串列來製作堆疊的優點是隨時可以動態改變串列長度,能有效利用記憶體資源,不過缺點是設計時,演算法較為複雜。

相關演算法如下:

class Node {    //堆疊鏈結點的宣告
constructor() {
this.data=0; //堆疊資料的宣告
this.next=null; //堆疊中用來指向下一個節點
}
}
top=null;
var isEmpty=()=> {
if(top===null)
return true;
else
return false;
}
//將指定的資料存入堆疊
var push=(data)=> {
new_add_node=new Node();
new_add_node.data=data; //將傳入的值指定為節點的內容
new_add_node.next=top; //將新節點指向堆疊的頂端
top=new_add_node; //新節點成為堆疊的頂端
}
//從堆疊取出資料
var pop=()=> {
if (isEmpty()) {
process.studout.write('===目前為空堆疊====\n');
return -1;
}
else {
ptr=top; //指向堆疊的頂端
top=top.next; //將堆疊頂端的指標指向下一個節點
temp=ptr.data; //取出堆疊的資料
return temp; //將從堆疊取出的資料回傳給主程式
}
}
class Node {    //堆疊鏈結節點的宣告
constructor() {
this.data=0; //堆疊資料的宣告
this.next=null; //堆疊中用來指向下一個節點
}
}
top=null;
var isEmpty=()=> {
if(top==null)
return true;
else
return false;
}
//將指定的資料存入堆疊
var push=(data)=> {
new_add_node=new Node();
new_add_node.data=data; //將傳入的值指定為節點的內容
new_add_node.next=top; //將新節點指向性堆疊的頂端
top=new_add_node; //新節點成為堆疊的頂端
}
//從堆疊取出資料
var pop=()=> {
if (isEmpty()) {
process.stdout.write('===目前為空堆疊===\n');
return -1;
}
else {
ptr=top; //指向堆疊的頂端
top=top.next; //將堆疊頂端的指標指向下一個節點
temp=ptr.data; //取出堆疊資料
return temp; //將從堆疊取出的資料回傳給主程式
}
}
// 主程式
const prompt = require('prompt-sync') ();
while (true) {
const i = parseInt(prompt('要推入堆疊,請輸入1,彈出則輸入0,停止操作則輸入-1: '));
if (i==-1)
break;
else if (i==1) {
const value = parseInt(prompt('請輸入元素值:'));
push(value);
}
else if (i==0)
process.stdout.write('彈出的元素為'+pop()+'\n');
}
process.stdout.write('===========================\n');
while (!isEmpty()) //將資料陸續從頂端彈出
process.stdout.write('堆疊彈出的順序為:'+pop()+'\n');
process.stdout.write('===========================\n');

 JS           list_strack.js

圖說演算法使用JavaScript(十)

5-3-3單向鏈結串列刪除節點

在單向鏈結型態的資料結構中,如果要在串列中刪除一個節點,如同一列火車拿掉原有的車廂,依據所刪除節點的位置有三種不同的情形:

演算法如下

top=head;
head=head.next;

演算法如下

ptr.next=tail;
ptr.next=null;

演算法如下

Y=ptr.next;
ptr.next=Y.next;

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

JS          del_node.js

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

var del_ptr=(head,ptr)=>{   //刪除節點副程式
	top=head;
	if (ptr.num==head.num) {  //[情形1]:刪除點在串列首
		head=head.next;
		process.stdout.write('已刪除第' +ptr.num+' 號員工 姓名:'+ptr.
			name+' 薪資:'+ptr.salary);
	}
	else {
		while (top.next!=ptr)  //找到刪除點的前一個位置
			top=top.next;
		if(ptr.next==null) {   //刪除在串列尾的節點
			top.next=null;
			process.stdout.write('已刪除第'+ptr.num+' 號員工 姓名:'+ptr.
				name+' 薪資:'+ptr.salary+'\n');
		}
		else{
			top.next=ptr.next;
			process.stdout.write('已刪除第 '+ptr.num+' 號員工 姓名:'+ptr.
				name+' 薪資:' +ptr.salary+'\n');
		}		
	}
	return head;
}

findword=0;
namedata=['Allen','Scott','Mary','John','Mark','Ricky',
		  'Lisa','Jasica','Hanson','Daniel','Axel','Jack'];
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]];
process.stdout.write('員工編號 薪水 員工編號 薪水 員工編號 薪水 員工編號 薪水\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();
}

head=new employee(); //建立串列首
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;
}
const prompt = require('prompt-sync')();
while(true)  {
	const findword = parseInt(prompt('請輸入要刪除的員工編號,要結束刪除過程,請輸入-1:'));
	if (findword==-1)  //迴圈中斷條件
		break;
	else {
		ptr=head;
		find=0;
		while (ptr!=null) {
			if (ptr.num==findword){
				ptr=del_ptr(head,ptr);
				find=find+1;
				head=ptr;
			}
			ptr=ptr.next;
		}
		if (find==0)
			process.stdout.write('//////////沒有找到///////////\n');
	}
}
ptr=head;
process.stdout.write('\t座號\t  姓名\t成績\n');  //列印剩餘串列資料
process.stdout.write('\t======================\n');  
while (ptr!=null)  {
	process.stdout.write('\t['+ptr.num+' ]\t[ '+ptr.name+' ]\t[ '
		+ptr.salary+']\n');
	ptr=ptr.next;
}

5-3-4單向鏈結串列的反轉

看完了節點的刪除及插入後,各位可以發現在這種具有方向性的鏈結串列結構中增刪節點是相當容易的一件事。而要從頭到尾印整個串列似乎也不難,不過如果要反轉過來列印就真的需要某些技巧了。我們知道在鏈結串列中的節點特性是知道下一個節點的位置,可是卻無從得知它的上一個節點位置,不過如果要將串列反轉,則必須使用三個指標變數。請看下圖說明:

演算法如下:

class employee{
constructor() {
this.num=0;
this.salary=0;
this.name='';
this.next=null;
}
}
var invert=(x)=> { //x為串列的開始指標
p=x; //將p指向串列的開頭
q=null; //q是p的前一個節點
while (p!=null) {
r=q; //將r接到q之後
q=p; //將q接到p之後
p=p.next //p移到下一個節點
q.next=r; //q連結到之前的節點
}
return q;
}

JS          rev_node.js

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

findword=0;
namedata=['Allen','Scott','Marry','John','Mark','Ricky',
			'Lisa','Jasica','Hanson','Daniel','Axel','Jack'];
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]];

head = new employee();//建立串列首
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;
}

ptr=head;
i=0;
process.stdout.write('原始員工串列節點資料:\n');
while (ptr !=null) { //列印串列資料
	process.stdout.write('['+ptr.num+'\t'+ptr.name+'\t'
							+ptr.salary+'] -> ');
	i=i+1;
	if (i>=3) { //三個元素為一列
		console.log();
		i=0;
	}
	ptr=ptr.next;
}

ptr=head;
before=null;
process.stdout.write('\n反轉後串列節點資料:\n');
while (ptr!=null) { //串列反轉,利用三指標
	last=before;
	before=ptr;
	ptr=ptr.next;
	before.next=last;
}
ptr=before;
while (ptr!=null) {
	process.stdout.write('['+ptr.num+'\t'+ptr.name+'\t'
							+ptr.salary+'] ->');
	i=i+1;
	if (i>=3) { //三個元素為一列
		console.log();
		i=0;
	}
	ptr=ptr.next;
}

PHP  陣列 函數

array_pop() 刪除陣列最後一個元素。
array_push() 將一個或多個元素加入末端。
array_reverse() 以相反的順序返回陣列。
array_shift() 刪除首個元素。
array_slice() 刪除指定位置的元素,返回陣列。

圖說演算法使用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);
}
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>";
}