owned this note
owned this note
Published
Linked with GitHub
###### tags: `Python`
# 時間複雜度 與 排序法簡介
**<a href="https://hackmd.io/@Mes/python_note" class="redlink">點此回到 Python筆記 目錄</a>**
# 前言
我們在寫程式的時候,同一個目的可能會有很多種寫法,而判斷這些寫法哪個比較好的其中一種判斷條件就是「時間複雜度」,什麼是時間複雜度呢? 它是用來約略判斷我們程式執行次數的一個符號,假設我們的輸入值為 n ,程式就會執行 n 次,那我們的時間複雜度就會記為 「O(n)」
那這篇是我覺得大家可以另外知道的,所以課本上沒有,那這篇我主要是參考胡程維老師的文章,網址在下方:
<a href = "https://medium.com/appworks-school/%E5%88%9D%E5%AD%B8%E8%80%85%E5%AD%B8%E6%BC%94%E7%AE%97%E6%B3%95-%E8%AB%87%E4%BB%80%E9%BA%BC%E6%98%AF%E6%BC%94%E7%AE%97%E6%B3%95%E5%92%8C%E6%99%82%E9%96%93%E8%A4%87%E9%9B%9C%E5%BA%A6-b1f6908e4b80" class = "redlink">初學者學演算法|談什麼是演算法和時間複雜度——程式麻瓜的程式知識課(三)</a>
<a href = "https://medium.com/appworks-school/%E5%88%9D%E5%AD%B8%E8%80%85%E5%AD%B8%E6%BC%94%E7%AE%97%E6%B3%95-%E5%BE%9E%E6%99%82%E9%96%93%E8%A4%87%E9%9B%9C%E5%BA%A6%E8%AA%8D%E8%AD%98%E5%B8%B8%E8%A6%8B%E6%BC%94%E7%AE%97%E6%B3%95-%E4%B8%80-b46fece65ba5" class = "redlink">初學者學演算法|談什麼是演算法和時間複雜度——程式麻瓜的程式知識課(四)</a>
<a href = "https://medium.com/appworks-school/%E5%88%9D%E5%AD%B8%E8%80%85%E5%AD%B8%E6%BC%94%E7%AE%97%E6%B3%95-%E6%8E%92%E5%BA%8F%E6%B3%95%E5%85%A5%E9%96%80-%E9%81%B8%E6%93%87%E6%8E%92%E5%BA%8F%E8%88%87%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F%E6%B3%95-23d4bc7085ff" class = "redlink">初學者學演算法|談什麼是演算法和時間複雜度——程式麻瓜的程式知識課(五)</a>
<a href = "https://medium.com/appworks-school/%E5%88%9D%E5%AD%B8%E8%80%85%E5%AD%B8%E6%BC%94%E7%AE%97%E6%B3%95-%E6%8E%92%E5%BA%8F%E6%B3%95%E9%80%B2%E9%9A%8E-%E5%90%88%E4%BD%B5%E6%8E%92%E5%BA%8F%E6%B3%95-6252651c6f7e" class = "redlink">初學者學演算法|談什麼是演算法和時間複雜度——程式麻瓜的程式知識課(六)</a>
<a href= "https://medium.com/appworks-school/%E5%88%9D%E5%AD%B8%E8%80%85%E5%AD%B8%E6%BC%94%E7%AE%97%E6%B3%95-%E5%BE%9E%E8%B2%BB%E6%B0%8F%E6%95%B8%E5%88%97%E8%AA%8D%E8%AD%98%E4%BD%95%E8%AC%82%E9%81%9E%E8%BF%B4-dea15d2808a3" class = "redlink">初學者學演算法|從費氏數列認識何謂遞迴——程式麻瓜的程式知識課(七)</a>
# <span class="purple">時間複雜度 O</span>
重點有三:
> **1. 大 O 符號是用來描述一個演算法在輸入 n 個東西時,總執行次數與 n 的關係。**
> **2. 演算法有多快不是以秒衡量,而是以步驟次數**
> **3. 紀錄最高次方的那一項,並忽略其所有的係數**
基本上就是一個符號,O 的後面我們會接一個 `()` ,小括號內我們會填一個次數的表達式,像是 n、n² 等等,代表了這段程式最多需要執行的次數,我們可以用下方的例子來看會比較好理解。
# 常見的時間複雜度與排序法
### 1. O(1)
時間複雜度為 O(1) 的演算法,代表著不管你輸入多少個東西,程式都會在同一個時間跑完,我們看看下方這個例子:
```python=
arr = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
```
這邊有一個 List,裡面包含了 a~h 八個字母,那假設我們已經知道這個 List 裡面全部的元素與他們的 index,那當我們想找到「d」時,我們只要輸入 n=3 就可以找到它了
```python=
n = int(input("> "))
print(arr[n])
```
由上面的例子我們能發現,無論 n 輸入了多少,執行次數都是 1 次,因此時間複雜度為 O(1)
</br>
### 2. O(n)
這邊我們沿用上方的陣列
```python=
arr = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
```
那假設我們現在只知道這個陣列裡面有 a~h 八個字母,但並不知道他們的順序 (index),這樣的話,如果我們想找到「d」,就需要把它「搜尋」出來了對吧! 那「搜尋」具體來說要怎麼做呢?
最簡單的方法就是從第一個開始,一個一個找(線性搜尋法),從第一個找到第八個,如果找到了就停止。
```python=
arr = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
counter = 0
for letter in arr:
if letter == 'd' :
print("找到d了,它的index為",counter)
break
counter += 1
```
我們可以發現在最糟的情況下,搜尋的次數最多會等於陣列的長度(n個),以上例來說就是 8 ,因此時間複雜度為 O(n),因為搜尋的次數最多就是 n 個。
<br>
### 3. O(logn) —— 二分搜尋法
那假設今天有個 List,裡面放了範圍在 1~999 內的數字,並且已經由小到大排好了,像是這樣:
```python=
number = [57, 58, 124, 144, 145, 157, 180, 205, 206, 396, 457, 458, 475, 492, 496, 504, 529, 559, 572, 610, 616, 653, 675, 702, 718, 725, 764, 835, 887, 915, 939, 994]
```
那如果我們想要知道這個 List 內有沒有「180」這個數怎麼辦呢?我們一樣要搜尋對吧?但如果這個 List 的元素有上百個,那用上方的線性搜尋法不是很浪費時間嗎?有沒有其他的搜尋方法呢?
有的,這就是我們接下來要談的二分搜尋法。 同時,說到 O(logn),最經典的例子便是它,要注意的是,我們的 log 是以二為底的。
那麼二分搜尋法該怎麼做呢? 具體步驟如下:
1.檢查上界與下屆中間的數字i
2.如果大於目標,便把下界設為i。反之,如果小於目標,就把上界設為i
3.回到步驟一
<center><a href="https://medium.com/appworks-school/%E5%88%9D%E5%AD%B8%E8%80%85%E5%AD%B8%E6%BC%94%E7%AE%97%E6%B3%95-%E5%BE%9E%E6%99%82%E9%96%93%E8%A4%87%E9%9B%9C%E5%BA%A6%E8%AA%8D%E8%AD%98%E5%B8%B8%E8%A6%8B%E6%BC%94%E7%AE%97%E6%B3%95-%E4%B8%80-b46fece65ba5"><img src="https://codimd.mcl.math.ncu.edu.tw/uploads/upload_2a72ed5fed623c1a8d354cf52cd2e61b.png" width="600"></a><br>圖片來源:<a href = "https://medium.com/appworks-school/%E5%88%9D%E5%AD%B8%E8%80%85%E5%AD%B8%E6%BC%94%E7%AE%97%E6%B3%95-%E5%BE%9E%E6%99%82%E9%96%93%E8%A4%87%E9%9B%9C%E5%BA%A6%E8%AA%8D%E8%AD%98%E5%B8%B8%E8%A6%8B%E6%BC%94%E7%AE%97%E6%B3%95-%E4%B8%80-b46fece65ba5">初學者學演算法|從時間複雜度認識常見演算法</a></center><br>
我們可以把它寫得更清楚,如下:
1. 決定好左邊界 L,右邊界 R
2. 取 M = (L+R)/2,作為中間的 index
3. 如果 array[M] == 要找的數,return M
4. 如果 array[M]>要找的數,表示 M~R 這一段數字都是不可能的,所以讓 R = M - 1
5. 如果 array[M]<要找的數,表示 L~M 這一段數字都是不可能的,所以讓 L = M + 1
6. 如果 R>=L,持續第 2 步,否則回傳 -1(代表找不到)
我們可以看看下面這個動圖,藍色代表的是左邊界 L,橘色代表的是右邊界 R,綠色代表的是中間點 M:
<center><a href="https://blog.techbridge.cc/2016/09/24/binary-search-introduction/"><img src="https://static.coderbridge.com/img/techbridge/images/huli/binary_search.gif"></a>動圖來源: <a href = "https://blog.techbridge.cc/2016/09/24/binary-search-introduction/">淺談二分搜尋法</a></center>
<br>
這邊的停止條件是「當L>R」的時候,就代表找不到了
因為 L 代表:最左邊的有可能的值。換句話說,假如有答案,一定在 >=L 的位置
R 代表的是:最右邊有可能的值,假如有答案,一定在 <=R 的位置
所以當L > R 的時候,>=L 跟 <=R 已經是空集合了,代表不可能有答案
那我們就來實作看看吧:
```python=
number = [57, 58, 124, 144, 145, 157, 180, 205, 206, 396, 457, 458, 475, 492, 496, 504, 529, 559, 572, 610, 616, 653, 675, 702, 718, 725, 764, 835, 887, 915, 939, 994]
L = 0
R = len(number)-1
finding_flag = False
print("搜尋目標為", end = "")
target = int(input(": "))
while (R >= L):
M = (L+R)//2
if (number[M] > target):
R = M-1
elif (number[M] < target):
L = M+1
elif (number[M] == target):
print("找到", target, "了,它的index為", M)
finding_flag = True
break
if (not finding_flag):
print("這個 List 內沒有這個元素")
```
經過一些數學運算,我們能發現執行的次數約為logn次 (n為 List 大小),因此時間複雜度為 O(logn)
二分法還有些其他的狀況,有興趣可以到<a href="https://blog.techbridge.cc/2016/09/24/binary-search-introduction/">這裡</a>看看
<br>
### 4. O(n²) —— 選擇排序法(Selection Sort)
前面的 List 我們都事先排好了,但如果今天我們有一個未排序的 List,那我們要將它的元素由小排到大,可以怎麼做呢? 解決這個問題的方法就是我們接下來要討論的排序法。
第一種方法是選擇排序法,這個排序法有兩個步驟:
1. 從「尚未排序的數字」中找最小值
2. 把這個最小值丟到「未排序數字」的最左邊
不停地重複這兩個動作,直到沒有未排序的數字,這就是選擇排序法!
假設現在有一個未排序的 List [41, 33, 17, 80, 61, 5, 55],那麼排序的情況會如下圖
<center><a href="https://medium.com/appworks-school/%E5%88%9D%E5%AD%B8%E8%80%85%E5%AD%B8%E6%BC%94%E7%AE%97%E6%B3%95-%E6%8E%92%E5%BA%8F%E6%B3%95%E5%85%A5%E9%96%80-%E9%81%B8%E6%93%87%E6%8E%92%E5%BA%8F%E8%88%87%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F%E6%B3%95-23d4bc7085ff"><img src="https://codimd.mcl.math.ncu.edu.tw/uploads/upload_70bca464eb96df6e2010fe04e618fbeb.png"></a><br>圖片來原:<a href = "https://medium.com/appworks-school/%E5%88%9D%E5%AD%B8%E8%80%85%E5%AD%B8%E6%BC%94%E7%AE%97%E6%B3%95-%E6%8E%92%E5%BA%8F%E6%B3%95%E5%85%A5%E9%96%80-%E9%81%B8%E6%93%87%E6%8E%92%E5%BA%8F%E8%88%87%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F%E6%B3%95-23d4bc7085ff">初學者學演算法|排序法入門:選擇排序與插入排序法——程式麻瓜的程式知識課(五)</a></center><br>
那我們就來實作看看吧:
```python=
number = [41,33,17,80,61,5,55]
length = len(number)
for i in range(length-1):
min_index = i
for j in range(i+1, length):
if number[min_index] > number[j]:
min_index = j
number[min_index], number[i] = number[i], number[min_index]
print(number)
```
我們迴圈會有兩層,而 i 代表的是已排序好的 List 的最後一個元素的index,也就是上圖藍色部分最後一個元素的index,j 代表的是未排序好的 List 的第一個元素,也就是上圖紅色部分的第一個元素,而 min_index 代表的是目前找到的最小值的index,以上圖來說就是橘色部分的index。
找最小值的動作會執行 $\frac{n^2+n}{2}$ 次,而把最小值往前丟的動作會做n次,因此程式執行的次數為 $\frac{n²+3n}{2}$ 次 (n為陣列大小,也可以說是輸入的資料數),時間複雜度為 O(n²)
### 5. O(n logn) —— 快速排序法(Quick Sort)
第二種方法是快速排序法,這是一個很常使用的排序法,他是一種「把大問題分成小問題處理」的 Divide and Conquer 方法。
Quick Sort的步驟主要有兩步
+ 在數列中任意挑選一個數,我們把它稱為pivot,然後調整數列,使得「所有在pivot左邊的數都比pivot還小,而在pivot右邊的數都比pivot大」。
+ 接著,將所有在pivot左邊的數視為「新的數列」,所有在pivot右邊的數視為「另一個新的數列」,「分別」重複上述步驟(選pivot、調整數列),直到分不出「新的數列」為止。
<center><br>
<img src="https://codimd.mcl.math.ncu.edu.tw/uploads/upload_02897ab90d8ce2c5dec817bd1180b08f.png">
</center>
<br>
以上圖為例,考慮數列 [9、4、1、6、7、3、8、2、5],我們選定 5 當 pivot,接著調整數列,使得 pivot 左邊的數 [4、1、3、2] 皆小於5,而 pivot 右邊的數 [9、8、6、7] 皆大於5。
那我們該如何做到這件事呢?這時候就要介紹「調整數列」的方法了,俗稱Partition。 Partition的思路就是把數列「區分」成「小於pivot」與「大於pivot」兩半。
詳細步驟如下:
首先我們要定義變數:
<center><br>
<img src="https://codimd.mcl.math.ncu.edu.tw/uploads/upload_6bf64dceec5987470d8c6893fdaf12ed.png">
</center><br>
+ pivot = array[end],以數列最後一個數做為pivot,pivot的「數值」可以是任意的,挑選矩陣的最後一個位置是為了方便index(j)的移動,也可以挑選任意位置。
+ front 為數列的「最前端」index。
+ end 為數列的「最尾端」index。
+ j 是讓pivot與其餘數值逐一比較的index,從front檢查到end-1(因為end是pivot自己)。
+ i 是一個旗子(檢查點),一開始設為-1,當發現了比pivot小的數值( arr[ j ] )時,便將旗子往後插一格(i++),然後把arr[i]與arr[j]互換,當排序要結束時,將 arr[i] 與 pviot 的互換,因此當排序結束時,所有在index(i)左邊的數,都比pivot小,所有在index(i)右邊的數,都比pivot大。
可以參考下面的圖<br>
<center>
<img src="https://codimd.mcl.math.ncu.edu.tw/uploads/upload_985cd27cb0f88ef92951a3f3f8851b5c.png
">
</center><br>
最後,將pivot與arr[i]互換就完成了調整<br>
<center>
<img src="https://codimd.mcl.math.ncu.edu.tw/uploads/upload_9c361540f2c6fb08a779ea210f5fc300.png
">
</center><br>
接下來只要對「左數列」與「右數列」重複執行這些動作,就能完成整個數列的調整啦!
<center>
<img src="https://codimd.mcl.math.ncu.edu.tw/uploads/upload_72aa03d7b0ef5e0e6a253f81ac85021d.png
">
</center><br>
那我們來實作看看吧,這邊我們用到了遞迴,如果看不懂得可以先看看下一個例子(費氏數列遞迴):
```python=
arr = [9, 4, 1, 6, 7, 3, 8, 2, 5]
def Partition (front, end):
pivot = arr[end]
i = front - 1
for j in range(front, end):
if (arr[j] < pivot):
i += 1
(arr[i], arr[j]) = (arr[j], arr[i])
i += 1
(arr[i], arr[end]) = (arr[end], arr[i])
return i
def QuickSort (front, end):
if (front < end):
pivot = Partition(front,end)
QuickSort(front, pivot -1)
QuickSort(pivot + 1, end)
n = len(arr)
print("一開始arr長這樣:", end = "")
print(arr)
QuickSort(0, n-1)
print("排序後長這樣:", end = " ")
print(arr)
```
此篇文章及圖片來原:[Comparison Sort: Quick Sort(快速排序法)](https://alrightchiu.github.io/SecondRound/comparison-sort-quick-sortkuai-su-pai-xu-fa.html)
<br>
### 6. O(2$^n$) —— 費氏數列遞迴
時間複雜度為 O(2^n) 的演算法,代表著執行步驟會是 2 的 n 次方。實務上來說,這樣的執行效率非常的慢,例如當輸入為 100 時,執行步驟就會暴增到 30 位數,等於即使每秒都能執行 1 百億個步驟,也需要個天荒地老 1 千億年才能完成。因此,這樣的時間複雜度是大部分工程師在設計演算法時想要避免的。
而這樣的時間複雜度,最常見的例子是以遞迴計算費波那契數列 (Fibonacci numbers)。
所謂費波那契數列,是指在一串數字中,每一項是前兩項的和。數學上的定義為:
+ 第 0 項 = 0
+ 第 1 項 = 1
+ 第 n 項 = 第 n-1 項 + 第 n-2 項
從上面的數學定義,我們可以簡單列出數列的 0 到 10 項為:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55。
知道了何謂費波那契數列後,接著我們就要先來解釋如何計算在程式中計算數列的第 n 項為何,以及為何它的時間複雜度為 2 的 n 次方。
計算費波那契數列第 10 項的程式碼如下:
```python=
def fibo(n):
if n == 0:
return 0;
elif n == 1:
return 1;
return fibo(n-1) + fibo(n-2)
print(fibo(10))
```
觀察上面的程式碼我們可以看到,程式碼的設計幾乎只是單純地把數學定義寫成程式碼。 但這個函式的回傳值好像怪怪的耶,為什麼又呼叫了自己一次呀? 這個就叫做遞迴,遞迴最核心的觀念就是「在函式中呼叫自己」
在上例中,當我們執行 print(fibo(10)) 時,就會執行 def 後面的五行程式碼,並把 10 作為 n 執行,最後把它 return 的值印出來。
但根據費波那契數列的定義,要知道第 n 項,我們就需要知道第 n-1 項與第 n-2 項,於是在程式碼中我們可以看到,在 n 不等於 0 或 1 時,fibo( ) 函式就會呼叫函式自己,只是這次是想要來計算第 n-1 項與第 n-2 項,所以就呼叫 fibo(n-1) 與 fibo(n-2)。
而要如何知道第 n-1 項是多少呢?我們這時就需要知道第 n-2 與第 n-3 項,如此類推,最後直到我們想要知道的項為第 0 項與第 1 項時,因為這兩項已經被定義了,所以我們就能一一解答前面的所有項了。
還是有點抽象嗎?讓我們以計算 n=5 時的費波那契數列為例,他在進行函式執行時的呼叫順序如下:
<center>

每一項都會按照定義往前呼叫它的前兩項。
圖片來源:[初學者學演算法|從費氏數列認識何謂遞迴——程式麻瓜的程式知識課(七)](https://medium.com/appworks-school/%E5%88%9D%E5%AD%B8%E8%80%85%E5%AD%B8%E6%BC%94%E7%AE%97%E6%B3%95-%E5%BE%9E%E8%B2%BB%E6%B0%8F%E6%95%B8%E5%88%97%E8%AA%8D%E8%AD%98%E4%BD%95%E8%AC%82%E9%81%9E%E8%BF%B4-dea15d2808a3)
</center>
從上面的圖可以觀察到,每一次函式往下的呼叫,最後都會停在 F(1) 或 F(0),因為這兩項是唯一在計算費波那契數列時唯一先被定義的。因此找到這兩項後,就可以開始往前加總出其他項的值,而往前加總的順序如下:
<center>

每一項在呼叫到 F(1) 或 F(0) 後,才會往回推算到該項。
圖片來源:[初學者學演算法|從費氏數列認識何謂遞迴——程式麻瓜的程式知識課(七)](https://medium.com/appworks-school/%E5%88%9D%E5%AD%B8%E8%80%85%E5%AD%B8%E6%BC%94%E7%AE%97%E6%B3%95-%E5%BE%9E%E8%B2%BB%E6%B0%8F%E6%95%B8%E5%88%97%E8%AA%8D%E8%AD%98%E4%BD%95%E8%AC%82%E9%81%9E%E8%BF%B4-dea15d2808a3)
</center>
了解了費式數列遞迴程式碼如何運作後,我們就可以分析他的時間複雜度了。
最簡單理解時間複雜度的方法就是直接使用觀察法。從上面的圖我們可以看到,每往下一層,每一項需要呼叫的次數就變成 2 倍,像 F(5) 要呼叫 F(4) + F(3) 、F(3) 要呼叫 F(2) + F(1) 等等。
而要找到數列的第 5 個數,我們需要的層數是剛好是 5 層,而推展到第 n 個數時,我們剛好需要 n 層。這個部分如果有點抽象的話,可以直接觀察上面的例子,圖中每一層的最左邊那項,是從 F(n) 一路遞減到 F(1),這樣剛剛好會是 n 層。
整理上面兩段如下,我們就可以求出最後時間複雜度約等於為 O(2^n)。
+ 每往下一層,步驟數變 2 倍
+ 總共有 n 層
+ 時間複雜度:O(2^n)
# 結言
排序法並不只有這些,原文內還有講了合併排序法與插入排序法,如果有興趣的可以到最上方點進連結看看。
那我們現在已經會了Python最基礎的語法,並且也了解了簡單判斷程式效率的方法,接下來大家可以一邊精進自己的語法能力,一邊找找自己有興趣的東西去實作。
<style>
.green {
color:#29E5A9;
}
.brown {
color:#990000;
}
.pink {
color:#DD9FBD;
}
.red {
color:#E71B18 ;
}
.blue {
color:#0b5394;
}
.purple {
color:#AC9FDD;
}
@-webkit-keyframes A
{
0% { color:#C10066;}
10% { color: #CC0000;}
20% { color: #E63F00; }
30% { color:#EE7700; }
40% { color: #DDAA00; }
50% { color:#EEEE00;}
60% { color: #99DD00;}
70% { color:#66DD00;}
80% { color: #00DDDD;}
90% { color: #0044BB;}
100% { color: #A500CC;}
}
#animation_title{
animation: A 3s ease 0s infinite alternate;
-webkit-animation: A 3s ease 0s infinite alternate;
}
</style>
<style>
a.redlink {
color:#DB1859;
}
a.redlink:link {
color:#DB1859;
text-decoration:none;
}
a.redlink:visiteid {
color:#DB1859;
text-decoration:none;
}
a.redlink:hover {
color:#19CABC;
text-decoration:none;
}
a.redlink:active {
color:#000000;
text-decoration:underline;
background:#FFFFFF;
}
</style>
<style type="text/css">
h1 {
font-size:;
color:#0b5394;
}
h2 {
font-size:;
color:#0b5394;
}
p {
font-size:;
color:;
}
h3 {
font-size: ;
color:#990000;
}
h4 {
font-size:;
color:#990000;
}
</style>
<style>
.green {
color:#29E5A9;
}
.brown {
color:#990000;
}
.pink {
color:#C18387;
}
.red {
color:#E71B18 ;
}
.blue {
color:#0b5394;
}
.purple {
color:#AC9FDD;
}
@-webkit-keyframes A
{
0% { color:#C10066;}
10% { color: #CC0000;}
20% { color: #E63F00; }
30% { color:#EE7700; }
40% { color: #DDAA00; }
50% { color:#EEEE00;}
60% { color: #99DD00;}
70% { color:#66DD00;}
80% { color: #00DDDD;}
90% { color: #0044BB;}
100% { color: #A500CC;}
}
#animation_title{
animation: A 3s ease 0s infinite alternate;
-webkit-animation: A 3s ease 0s infinite alternate;
}
</style>
<style>
a.redlink {
color:#DF2F6A;
}
a.redlink:link {
color:#DF2F6A;
text-decoration:none;
}
a.redlink:visiteid {
color:#DF2F6A;
text-decoration:none;
}
a.redlink:hover {
color:#19CABC;
text-decoration:none;
}
a.redlink:active {
color:#000000;
text-decoration:underline;
background:#FFFFFF;
}
</style>
<style type="text/css">
h1 {
font-size:;
color:#0b5394;
}
h2 {
font-size:;
color:#0b5394;
}
p {
font-size:;
color:;
}
h3 {
font-size: ;
color:#990000;
}
h4 {
font-size:;
color:#990000;
}
</style>