集合
集合的種類
在上一章的數論當中,那些數形成了《集合》,集合的元素個數,大致可分為下列情況:
- 有限集合:例如小於 10 的自然數,介於 10 到 20 之間的整數等等。
- 無限集合:例如整數、有理數、實數、複數所形成的集合。
但是無限集合當中,又可以分為《可數無限集合》和《不可數無限集合》。
所謂的《可數無限集合》,是可以和《自然數》一對一對映的集合,而《不可數無限集合》,則是比《自然數》多,可以證明無法和《自然數一對一對映的集合,像是《實數》、《複數》等等。
康托爾(Cantor) 提出一個著名的 《對角證法》,證明了實數的數量為 《不可數無限多》 ,無法和《自然數》一對一對映!
這種《探討集合內容與運算》、《界定集合邊界》的數學,就稱為集合論。
集合論可以說是《整個數學大廈》的基礎,在數學發展的過程當中,集合論曾經出現過一些問題,而這些問題往往醞釀出嚴重的《數學危機》。
這些危機,往往和一些《悖論》有關!
悖論
第一次數學危機
在公元前五世紀的古代,《畢達哥拉斯》學派是數學的一個神祕學派,它們相信所有的數都可以化成 p/q 這樣的比例 (ratio) ,也就是《有理數》 (rational number, 通常用 Q 代表)。
但是畢達哥拉斯學派弟子希伯斯發現。他以幾何方法證明 $\sqrt{2}
$ 無法用《比例》表示,也就是無法寫成 p/q。換句話說 $\sqrt{2}
$ 不是有理數,或者說是無理數。
後來希伯斯觸犯學派章程,將無理數透露給外人,因而被處死,其罪名竟然等同於「瀆神」。這就是所謂的 第一次數學危機。
第一次的數學危機,導致了《無理數》的發現,因此才會建立《實數》的概念,甚至後來還建立了《複數》。
第二次數學危機
第二次數學危機,是由《無窮小》這個概念所引發的,因為《無窮小》所造成的弔詭現象,古希臘的《芝諾》提出了幾個悖論:
芝諾的第一個悖論稱為《飛矢不動論》,描述如下:
芝諾問他的學生 「一支射出的箭是動的還是不動的?」
「那還用說,當然是動的。」
「確實是這樣,在每個人的眼裡它都是動的。
可是,這支箭在每一個瞬間裡都有它的位置嗎?」
「有的,老師。」
「在這一瞬間裡,它佔據的空間和它的體積一樣嗎?」
「有確定的位置,又佔據著和自身體積一樣大小的空間。」
「那麼,在這一瞬間裡,這支箭是動的,還是不動的?」
「不動的,老師」
「這一瞬間是不動的,那麼其他瞬間呢?」
「也是不動的,老師」
「所以,射出去的箭是不動的?」
芝諾的第二個悖論是《阿基里斯永遠追不上烏龜》,那阿基里斯就是希臘神話中最勇猛的英雄,參與了特洛伊戰爭,被稱為「希臘第一勇士」。
芝諾說,這個跑得超快的勇士,永遠追不上一隻爬得超慢的烏龜!
芝諾悖論之「阿基里思與烏龜」 論述如下:
動得最慢的物體不會被動得最快的物體追上。
由於追趕者首先應該達到被追者出發之點,此時被追者
已經往前走了一段距離。
因此被追者總是在追趕者前面。
來源:—亞里士多德, 物理學 VI:9, 239b15
Zeno's paradoxes:Achilles and the tortoise
– In a race, the quickest runner can never
overtake the slowest, since the pursuer
must first reach the point whence the
pursued started, so that the slower must
always hold a lead.
在HPM(數學史與數學教學,History and Pedagogy of Mathematics)這份研究中,列出了相關數學家對無窮小的看法:
人物 | 年代 | 對無窮小量的觀點,或處理方法 |
---|---|---|
芝諾(希臘語:Ζήνων) | 約前490年-前430年 | 提出飛矢不動論、阿基里斯跑不過烏龜等悖論 |
歐幾里得 | 公元前300年 | 窮竭法:他們相信用間接法才能使面積問題獲得嚴格證明。 |
卡瓦列里(B. Cavalieri) | 1598-1647 | 把無窮小量的辦法推進了一步(見祖暅原理) |
沃利斯(J. Wallis) | 1616-1703 | 他對極限的定義「含有正確的想法,但用詞不嚴謹」 |
萊布尼茲 | 1646-1716 | 其演算法很成功,但「對概念不太確定」。他對於「消失中的量」的立場是複雜的,而且隨時間而變。 |
歐拉 | 1707-1783 | 獲得了很多重要結果,但不考慮真正無窮小量帶來的困難。其觀點受十七世紀典型的科學思維框架影響。 |
達朗貝爾(J. d’Alembert) | 1717-1783 | 拒絕承認「消失中的量」。他給出過極限的定義,但措辭不明確。 |
拉格朗日 | 1736-1813 | 也拒絕承認無窮小量,企圖把微積分歸結為代數。 |
柯西 | 1789-1857 | 其寫下的定義至今依然通用,由當時可以使用的數學語言寫成。 |
這個數學危機,一直從古希臘時代就存在了,直到 19 世紀初柯西(法語:Augustin Louis Cauchy) 寫了一系列的分析學論文,將微積分嚴格公理化之後,才算解決了此一問題。
19世紀微積分學的準則並不嚴格,柯西拒絕當時微積分學的說法,並定義了一系列的微積分學準則。他一生共發表800多篇論文。其中較為有名的是《分析教程》、《無窮小分析教程概論》和《微積分在幾何上的應用》。
柯西建構起我們大學時期微積分所要學的那種無窮小 $\epsilon
$ 理論,讓微積分成為一門嚴格而沒有矛盾的數學,解決了第二次的數學危機。
這門建構《微積分基礎》的數學,就稱為《分析學》!
第三次數學危機
第三次數學危機的主角,正是集合論,關鍵人物是《康托爾》和《羅素》。
1870年代由《康托爾及理察·戴德金》提出的《樸素集合論》,後來被更加精確地構建為 《公理化集合論》 ,以解決《樸素集合論》中的《悖論》問題。
最著名的《樸素集合論》悖論,是羅素所提出的,描述如下
由《所有不包含集合自身的集合所構成的集合》用數學符號表示為:
A={x|x ∉ x}
問題是: A 自己到底算不算 A 的成員呢?
仔細分析、您可以看到其中的矛盾!
羅素和懷海德在撰寫《數學原理》這部巨著快完成的時候,發現了上述的悖論,還有理髮師悖論,結果羅素在書的後記裏說:
科學家遇到最不幸的事情,莫過於當一件偉大的工作完成的時候,它的基礎卻早已倒塌
對於一般集合,不管是有限或無限,可數或不可數,我們通常可以寫出這樣的判斷函數,但是對於那些特別詭異的問題,像是《羅素的集合悖論》、《理髮師悖論》等等之類的集合,我們沒有辦法寫函數來求解,因為這種集合會造成數學矛盾,電腦也無法百分之百正確判斷!
為了解決集合論的悖論問題,數學家們可以說是想盡了各種辦法。
從《羅素悖論》發現開始,《數學家們》逐漸發展出《公理化集合論》,但是這樣的集合論雖然可以透過無法構造出《羅素悖論集合》而避開問題,但由於是採用《一階邏輯出發的》,所以不太適合用在電腦上。
更糟糕的是,即使採用了《公理化集合論》,仍然會遭遇一些《悖論》問題,這些《悖論》比較不嚴重,像是 《布拉利-福爾蒂悖論》 ,還有 《康托爾悖論》。
要瞭解這些悖論的成因,首先要理解何謂 《序數》 與 《基數》,然後看看《康托爾》等人是如何定義序數和基數的。
直覺上、基數代表集合的大小,像是集合 {1,2,3} 的基數就是 3,而序數代表順序,像是《第一、第二、第三》等等。
==== 以下段落來自 維基百科:基數 =====
康托爾在1874年-1884年引入最原始的集合論(現稱樸素集合論)時,首次引入基數概念。
他最先考慮的是集合{1,2,3}和{2,3,4},它們並非相同,但有相同的基數。驟眼看來,這是顯而易見,但究竟何謂兩個集合有相同數目的元素?
康托爾的答案,是透過所謂的一一對應,即把兩個集合的元素一對一的排起來——若能做到,兩個集合的基數自然相同。這答案,容易理解但卻是革命性的,因為用相同的方法即可比較任意集合的大小,包括無窮集合。
最先被考慮的無窮集合是自然數集 N = {1, 2, 3, …}及其無限子集。他把所有與 N 能一一對應的集為可數集。令康托爾意外的是,原來N的所有無限子集都能與N一一對應。他把N的基數稱為 N0,是最小的 阿列夫數 (往上還有 N1, N2, …)。
康托爾發現,原來有理數集合與代數數集合也是可數的。於是乎在1874年初,他嘗試證明是否所有無限集合均是可數,其後他得出了實數集不可數的結論。原先的證明用到了涉及區間套的複雜論證,而在他1891年的論文中,他以簡單而巧妙的對角論證法重新證明了這一結果。實數集的基數,記作 C,代表連續統。
接著康托爾構作一個比一個大的集合,得出一個比一個大的基數,而這些巨大集合的元素已不可如實數般書寫出來。因此關於基數的一般理論,需要一個新的語言描述,這就是康托爾發明集合論的主因。
康托爾隨後提出 《連續統假設》:該假設認為 C 就是第二個超窮基數 N1,即繼 N0 之後最小的基數。
對於《連續統假設》成立或不成立,可以分為兩種情況:
(a). 成立: N0 < N1 = Card(R) (b). 不成立: N0 < N1 < Card(R)
康托爾當時採用的集合論稱為《樸素集合論》,這是在《羅素悖論被提出之前的事情了》!
後來為了處理悖論,數學家們發展出了《公理化集合論》,像是 ZFC 這樣的公理系統。
ZFC 發展出來之後,數學家們開始試圖去證明《連續統假設》,結果卻發現證明不了。
後來《哥德爾》和《柯恩》分別證明了連續統假設和集合論其餘公理是相容且獨立的。
1940年《哥德爾》證明了上述的 (a) 式與集合論其餘公理相容。
1963年《柯恩》證明了上述 (b) 式與集合論其他公理也相容。
所以現在我們知道《連續統假設》是不能證明的,即接受或否定它會得出兩套以上不同但邏輯上可行的公理化集合論。
這個結果和《歐氏幾何》的平行公設非常相似,承認與否認《連續統假設》都可以建構出完整且一致的集合論。
無限集的基數
最小的無限集合是自然數集。{1, 2, 3, 4,…, n, …} 與 {2, 4, 6, 8, …, 2n, …} 基數相同,因為可以讓前一集合的 n 與後一集合的 2n 一一對應。從這個例子可以看出,對於一個無窮集合來說,它可以和它的一個真子集有相同的基數。
無限集合具有下列特性:(下列四種定義等價)
- 它不與任何自然數等勢 (等勢代表具有同樣基數,可以一對一映射)。
- 它有超過一個等勢的序數。(兩種以上可以一對一映射的序數)
- 它有至少一個真子集和它等勢。
- 存在由自然數集到它的單射。
對角證法
《康托爾》透過一對一映射定義基數,發現《自然數、整數、有理數、代數數》之間都可以一對一映射。
於是《康托爾》開始企圖證明《所有無限集合均是可數的這件事情》。
結果大感意外的是,他反而證明出了《實數集是不可數的》這個定理。
該定理原先的證明相對複雜,但是後來在 1891 年的論文中,他以簡單而巧妙的 《對角論證法》 重新證明了《實數集是不可數的》。
證明了《實數集不可數》之後,《康托爾》實數集的基數,記作 C,代表連續統。
得出了實數集不可數的結論。
實數集合的大小
用下列方程式可以將 (0,1) 之間的實數集合,一對一映射到所有實數上。
$y=\frac{2x-1}{x^2}
$
連續統假設
對每一個基數,存在一個最小比它大的基數。這在自然數當然是對的。自然數集的基數是 N0,康托爾稱下一個為 N1,相類似的,還定義了如下序列:N0, N1, N2, ….。
(注意 $C = 2^{N0}
$ )
連續統假設猜想,就是 C=N1 。
連續統假設是與一般集論公理(即Zermelo-Fraenkel公理系統加上選擇公理)是獨立的。
更一般的廣義連續統假設假設是 $N_{n+1}=2^{N_n}
$ ,也就是對所有無窮基數 N,都不存在介乎 N 與 $2^N
$ 之間的基數。
必須特別注意的是,這個假設沒有被證明,康托爾花了一輩子,最後還陷入赤貧,並且罹患躁鬱症,但始終沒有辦法證明連續統假說。
集合論的程式實作方法
集合之間可以進行《聯集、交集、差集》等運算,這種《集合與運算》形成了 《集合代數》。
有限集合
在電腦中,要表達集合的方式有很多,像是用《陣列》或《字典》都可以代表集合。
在 javascript 當中,用《字典或陣列》代表《有限集合》是最方便的做法。
舉例而言,假如果們想用一個代表《血型》,用陣列就可以宣告如下:
var bloodType = ["A", "B", "AB", "O"];
JavaScript 中的字典,傳統做法是用物件 (Object)。血型
var bloodDictionary = {
A: "隨興",
B: "正直",
AB: "固執",
O: "樂觀"
}
然而、由於用物件充當字典會有一些副作用,像是「成員函數會被當作集合內容,只能用字串代表元素」等等問題,於是在 ECMAScript 6 (ES6) 版的標準當中,決定加入 Set, Map 等型態,以便能避開這些問題。
以下是這些新型態《集合與映射》的用法:
檔案: es6setmap.js
var c = console;
var bloodSet = new Set(["A", "B", "AB", "O"]);
var A = ["A","隨興"], B=["B","正直"], AB=["AB", "固執"], O=["O", "樂觀"];
var bloodMap = new Map([A,B,AB,O]);
c.log('bloodSet.has(A)=', bloodSet.has("A"));
c.log('bloodSet.has(C)=', bloodSet.has("C"));
c.log('bloodMap.has(A)=', bloodMap.has("A"));
c.log('bloodMap.has(C)=', bloodMap.has("C"));
c.log('bloodMap.get(A)=', bloodMap.get("A"));
c.log('bloodMap.get(C)=', bloodMap.get("C"));
執行結果:
D:\Dropbox\gitbook\rlab\code\set>node es6setmap.js
bloodSet.has(A)= true
bloodSet.has(C)= false
bloodMap.has(A)= true
bloodMap.has(C)= false
bloodMap.get(A)= 隨興
bloodMap.get(C)= undefined
除此之外、ES6 還加入了 WeakSet, WeakMap 等型態,其鍵值(key) 只能是物件,不能是基本型態,這種設計方式有助於垃圾蒐集機制快速回收記憶體 (不會留下一大堆無法回收的物件)。
無限集合
上述範例是對於有限集合而言,但是對於無限集合 (包含可數無限和不可數無限集合) ,我們應該怎麼處理呢?
對於電腦而言,通常無法代表《無限》的事物,因此我們用有上下限的《整數》來代表數學上的《整數》,用《浮點數》來代表數學上的《實數》。
如果我們要判斷一個數字是否在某集合內,可以撰寫函數來達成這件事,只是我們判斷的對象,通常是《電腦裡能表達的那種數字型態》,在 javascript 裏通常就是 Number 。
舉例而言,我們可以寫出下列來判斷一個數是否為《自然數》:
檔案:naturalNumber.js
var c = console;
function natural(n) {
return n>=0 && n%1===0;
}
c.log("natural(3)=", natural(3));
c.log("natural(3.8)=", natural(3.8));
c.log("natural(-5)=", natural(-5));
執行結果:
D:\Dropbox\gitbook\rlab\code\set>node biggerThan.js
natural(3)= true
natural(3.8)= false
natural(-3)= false
甚至對於像質數這種數學上較難的問題,我們也可以寫個程式來判斷:
檔案: isPrime.js
var c = console;
function isPrime(n) {
for (var i=2; i<n; i++)
if (n % i === 0)
return false;
return true;
}
c.log("isPrime(27)=", isPrime(27));
c.log("isPrime(13)=", isPrime(13));
執行結果:
D:\Dropbox\gitbook\rlab\code\set>node isPrime
isPrime(27)= false
isPrime(13)= true
同樣的,對於《浮點數》我們也可以寫類似的判斷函數,以判斷某數是否在指定集合中。
程式解析
Rlab 的 set.js 模組當中定義了各種常見集合與操作,以下是原始程式碼。
檔案: lib/set.js
var S = {}
var I = require("./integer");
S.PI = Math.PI;
S.E = Math.E;
extend = S.extend = Object.assign;
// ================= Rule =====================
var check = S.check = S.assert = function(cond, msg) {
if (cond)
console.log("O:"+msg);
else {
console.log("X:"+msg);
if (S.throwError) throw Error('check fail!');
}
}
be = S.be =function(msg,cond) { return check(cond, msg) }
S.proto=function(o) { return Object.getPrototypeOf(o) }
// relation
var eq=S.eq=function(a,b) {
return (typeof a === typeof b) && a.toString()===b.toString()
}
S.neq=function(a,b) { return !S.eq(a,b) }
S.leq=function(a,b) { return a<=b }
S.geq=function(a,b) { return a>=b }
S.lt =function(a,b) { return a<b }
S.gt =function(a,b) { return a>b }
// ========= type checking =================
S.yes=function(a) { return true }
S.no=function(a) {return false }
S.isBool=function(a) {
return typeof a === 'boolean' || a instanceof Boolean
}
S.isFunction=function(a) {
return typeof a==='function' || a instanceof Function
}
S.isString=function(a) {
return typeof a==='string' || a instanceof String
}
S.isObject=function(a) {
return typeof a==='object' || a instanceof Object
}
S.isArray=function(a) {
return a instanceof Array
}
S.isUndefined=function(a) {
return typeof a === 'undefined'
}
S.isSet=function(a) {
return a instanceof Set
}
S.isFloat=S.isNumber=function(a) {
return typeof a === 'number' || a instanceof Number
}
S.isInteger=function(a) { return S.isFloat(a) && a%1===0 }
S.isZero=function(a) { return a===0 }
S.isPositive=function(a) { return a>0 }
S.isNegative=function(a) { return a<0 }
S.isEven=function(a) { return (S.isInteger(a) && a%2===0) }
S.isOdd=function(a) { return (S.isInteger(a) && a%2===1) }
// ========== Random ==============
S.random=function(a,b) {
return a+Math.random()*(b-a);
}
S.randomInt=function(a,b) {
return Math.floor(S.random(a,b));
}
S.sample=function(a) {
return a[S.randomInt(0,a.length)];
}
// ========= Set ===================
Set.prototype.union = function(setB) {
var union = new Set(this);
for (var elem of setB) {
union.add(elem);
}
return union;
}
Set.prototype.intersection = function(setB) {
var intersection = new Set();
for (var elem of setB) {
if (this.has(elem)) {
intersection.add(elem);
}
}
return intersection;
}
Set.prototype.difference = function(setB) {
var difference = new Set(this);
for (var elem of setB) {
difference.delete(elem);
}
return difference;
}
Set.prototype.enumerate = function(n) {
var array=[], values = this.values();
for (var i=0; i<n; i++) {
array.push(values.next().value);
}
return array;
}
class EnumSet {
constructor(enumHead) {
this.set = new Set(enumHead);
this.enumHead = S.isUndefined(enumHead)?[]:enumHead;
}
add(e) { this.set.add(e) }
has(e) { return this.set.has(e) }
sample(n) {
if (S.isUndefined(n))
return S.sample(this.enumHead);
else {
var a=[];
for (var i=0; i<n; i++) a.push(this.sample());
return a;
}
}
enumerate() { return this.enumHead }
intersection(y) {
var x=this, xy=new EnumSet();
xy.has=function(e) { return x.has(e)&&y.has(e) }
return xy;
}
union(y) {
var x=this, xy=new EnumSet();
xy.has=function(e) { return x.has(e)||y.has(e) }
return xy;
}
difference(y) {
var x=this, xy=new EnumSet();
xy.has=function(e) { return x.has(e)&&!y.has(e) }
return xy;
}
symmetricDifference(y) {
var x=this;
return x.union(y).difference(x.intersection(y));
}
cartesianProduct(y) {
var x=this, xy=new EnumSet();
xy.has=function(e) { return x.has(e[0]) && y.has(e[1]) }
return xy;
}
}
S.Set = EnumSet
function steps(a,b,step) {
var array=[];
for (var i=a; i<=b; i+=step)
array.push(i);
return array;
}
var enumFloat = [-3.2,-1, 0, 1, 2.3, 4.7];
var enumInt = [-10,-5,-1,0,1,3,5,6];
var enumN0 = steps(0,10,1);
var enumN1 = steps(1,10,1);
var enumOdd = steps(1,15,2);
var enumEven = steps(2,15,2);
var enumPrime = [2,3,5,7,11,13,17,19,23,29,31,37];
var enumAll = ["hi", 5, Math.PI, EnumSet, S.isBool, enumPrime, new Set() ];
// 全體集合
S.All = new EnumSet(enumAll);
S.All.has = S.yes;
// 空集合
S.Empty = new EnumSet([]);
S.Empty.has = S.no;
// 浮點數集合
S.Float=new EnumSet(enumFloat);
S.Float.has=S.isFloat;
// 整數集合
S.Z=S.Integer=new EnumSet(enumInt);
S.Z.has=S.isInteger;
// 自然數集合 N0
S.N0=new EnumSet(enumN0);
S.N0.has=(e)=>S.isInteger(e)&&e>=0;
// 自然數集合 N1
S.N1=new EnumSet(enumN1);
S.N1.has=(e)=>S.isInteger(e)&&e>1;
// 偶數集合
S.Even=new EnumSet(enumEven);
S.Even.has=S.isEven;
// 奇數集合
S.Odd=new EnumSet(enumOdd);
S.Odd.has=S.isOdd;
// 質數集合
S.Prime=new EnumSet(enumPrime)
S.Prime.has=I.isPrime;
// 有限集合 0...n-1
S.Finite=(n)=>new EnumSet(steps(0,n-1,1));
// 羅素集合悖論
S.RussellSet=new EnumSet(enumAll);
S.RussellSet.has=function(e) { return !e.has(e) }
module.exports=S;
接著我們示範如何使用這個模組:
檔案: example/setEx.js
var R = require("../rlab");
var S10 = R.Finite(10);
print('S10.sample(5)=', S10.sample(5));
print('Float.sample(5)=', R.Float.sample(5));
print('Z.sample(5)=', R.Z.sample(5));
print('Even.sample(5)=', R.Even.sample(5));
print('Odd.sample(5)=', R.Odd.sample(5));
print('Prime.sample(5)=', R.Prime.sample(5));
print('Prime.enumerate()=', R.Prime.enumerate());
print('Empty.sample(5)=', R.Empty.sample(5));
var OddPrime = R.Odd.intersection(R.Prime);
OddPrime.enumHead = [3,5,7,11,13];
print('OddPrime.sample(5)=', OddPrime.sample(5));
print('OddPrime.has(71)=', OddPrime.has(71));
print('OddPrime.has(70)=', OddPrime.has(70));
print('OddPrime.has(69)=', OddPrime.has(69));
var OddXPrime = R.Odd.cartesianProduct(R.Prime);
print('OddXPrime.has([9,71])=', OddXPrime.has([9, 71]));
print('OddXPrime.has([8,71])=', OddXPrime.has([8, 71]));
print('OddXPrime.has(71)=', OddXPrime.has(71));
// RussellSet
print('RussellSet.has(Odd)=', R.RussellSet.has(R.Odd));
print('RussellSet.has(RussellSet)=', R.RussellSet.has(R.RussellSet));
執行結果:
D:\Dropbox\github\rlab\example>node setEx.js
S10.sample(5)= [ 1, 3, 9, 6, 3 ]
Float.sample(5)= [ 0, 4.7, 4.7, 4.7, 1 ]
Z.sample(5)= [ 5, 3, -10, 6, 5 ]
Even.sample(5)= [ 8, 14, 10, 6, 8 ]
Odd.sample(5)= [ 1, 11, 1, 1, 3 ]
Prime.sample(5)= [ 2, 29, 3, 31, 5 ]
Prime.enumerate()= [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37 ]
Empty.sample(5)= [ undefined, undefined, undefined, undefined, undefined ]
OddPrime.sample(5)= [ 7, 13, 7, 5, 7 ]
OddPrime.has(71)= true
OddPrime.has(70)= false
OddPrime.has(69)= false
OddXPrime.has([9,71])= true
OddXPrime.has([8,71])= false
OddXPrime.has(71)= false
RussellSet.has(Odd)= true
D:\Dropbox\github\rlab\lib\set.js:216
S.RussellSet.has=function(e) { return !e.has(e) }
^
RangeError: Maximum call stack size exceeded
at EnumSet.S.RussellSet.has (D:\Dropbox\github\rlab\lib\set.js:216:26)
at EnumSet.S.RussellSet.has (D:\Dropbox\github\rlab\lib\set.js:216:42)
at EnumSet.S.RussellSet.has (D:\Dropbox\github\rlab\lib\set.js:216:42)
at EnumSet.S.RussellSet.has (D:\Dropbox\github\rlab\lib\set.js:216:42)
at EnumSet.S.RussellSet.has (D:\Dropbox\github\rlab\lib\set.js:216:42)
at EnumSet.S.RussellSet.has (D:\Dropbox\github\rlab\lib\set.js:216:42)
at EnumSet.S.RussellSet.has (D:\Dropbox\github\rlab\lib\set.js:216:42)
at EnumSet.S.RussellSet.has (D:\Dropbox\github\rlab\lib\set.js:216:42)
at EnumSet.S.RussellSet.has (D:\Dropbox\github\rlab\lib\set.js:216:42)
at EnumSet.S.RussellSet.has (D:\Dropbox\github\rlab\lib\set.js:216:42)
小結
集合論是所有數學的基礎建設,但由於可能導致《悖論》的關係,因此數學上必須小心建構。
有些《悖論》的原因是我們想像力還沒到那種境界,像是《畢達哥拉斯學派》認為所有數都可以寫成 p/q 這種形式的《有理數》,沒辦法將想像力從《有理數》提升到《無理數》,就是一個經典的範例!
同樣的、從《實數》到《複數》的路上,也必須先承認 $\sqrt{-1}
$ 是有意義的數字,才能夠釋放出那些對圓周運動非常有用的數學理論,《複數》在電磁學和訊號處理上的用途,是非常令人驚豔的!
另外的一些《悖論》,則是由於《數學基礎定義得不夠嚴謹》而產生的,像是《無限小》的概念經過《柯西》的《分析學》之後,就形成了一個相當嚴密的數學體系。
還有一些《悖論》,標示著《某些事物能力的極限》,像是羅素的《集合悖論、理髮師悖論》,還有《圖靈的停止問題、哥德爾不完備定理》等等,這些問題通常是我們無法 100% 完全正確解決的。
但是、這並不代表我們對這些問題完全無能為力。舉例而言、電腦的防毒問題,也就是判斷一個程式是否為病毒的問題,應該也是個《無法完全正確解決》的問題,但是我們還是可以寫出《檢查病毒》的程式,雖然無法100%正確,但是只要正確率夠高,對我們還是會很有幫助的!
《集合論》在經過羅素的《集合悖論、理髮師悖論》之後,被 Cantor 與後繼者改造成為《公理化集合論》,並透過《一階邏輯》進行了嚴密的定義,這讓集合論裏不會出現《自相矛盾集合》的現象,但是這並不代表所有問題都可以被解決了,一旦加上《數與加減乘除》等運算之後,就會發現還是有些問題是無法解決的。
像是《哥德爾不完備定理》中就證明了一個命題:《在涵蓋數論體系的數學世界中,一致性和完備性是無法兼得的》。
因此、有些定理是對的,但是我們證明不出來,萬一我們真的證明出來了,而且那個證明是對的,這時請先別高興,因為這代表你的公理系統已經出錯了!(整個數學大廈已然倒塌 ….)
以上就是我們從《電腦》的角度來看集合論的一些想法,或許很少《科學計算》與《數值分析》的書會談論這樣的主題,但是這本書希望盡可能涵蓋更多的《數學、科學與程式》之間的主題,因此您將會在後續的章節當中,繼續看到很多類似這樣的數學科學主題,而這也是筆者寫這本書的主要出發點!