圖說演算法使用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'];